文章目录
- ARM
- 寄存器
- 1. 通用寄存器
- 2. 向量寄存器
- 3. 特殊寄存器
- 4. 状态寄存器CPSR
- ARM64约定
- 常见指令
- MOV
- ADD
- SUB
- MUL
- SIDV, UDIV
- AND
- ORR
- EOR
- STR
- LDR
- STP
- LDP
- SCVTF, FCVTZS
- 比较指令
- 位移指令
- 跳转指令
- 问题1
ARM
寄存器
1. 通用寄存器
31个通用寄存器(R0~R30),每个寄存器可以存取一个64位大小的数,当使用 X0-X30时,是一个64位的数,当使用 W0-W30访问时,是一个32位的数,为寄存器的低32位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKx2zhAq-1628126375081)(./通用寄存器1.png)]
2. 向量寄存器
(也可以说是 浮点型寄存器)每个寄存器的大小是 128 位的。 分别可以用Bn、Hn、Sn、Dn、Qn的方式来访问不同的位数。
由图可以看出:
- Bn: 一个 Byte的大小,即 8 位;
- Hn: half word,即 16 位;
- Sn: single word,即 32 位;
- Dn: double word,即 64 位;
- Qn: quad word,即128 位。
3. 特殊寄存器
- sp: (Stack Pointer),栈顶寄存器,用于保存栈顶地址;
- fp(x29): (Frame Pointer)为栈基址寄存,用于保存栈底地址;
- lr(x30): (Link Register) ,保存调用跳转指令 bl 指令的下一条指令的内存地址;
- zr(x31): (Zero Register),xzr/wzr分别代表 64/32 位,其作用就是 0,写进去代表丢弃结果,读出来是 0;
- pc: 保存将要执行的指令的地址(有操作系统决定其值,不能改写)。
4. 状态寄存器CPSR
CPSR (Current Program Status Register)和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义;而 CPSR 寄存器是按位起作用的,即,每一位都有专门的含义,记录特定的信息;如下图注: CPSR 寄存器是 32 位的。
- CPSR 的 低8位(包括 I、F、T 和 M[4:0])称为控制位,程序无法修改,除非 CPU 运行于 特权模式 下,程序才能修改控制位。
- N、Z、C、V 均为条件码标志位;其内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行:
- N(Negative)标志: CPSR 的第 31 位是 N,符号标志位;记录相关指令执行后其结果是否为负数,如果为负数,则 N = 1;如果是非负数,则 N = 0。
- Z(Zero)标志: CPSR 的第 30 位是 Z,0标志位;记录相关指令执行后,其结果是否为0,如果结果为0,则 Z = 1;如果结果不为0,则 Z = 0。
- C(Carry)标志: CPSR 的第 29 位是C,进位标志位:
- 加法运算:当运算结果产生了 进位 时(无符号数溢出),C = 1,否则 C = 0 ;
- 减法运算(包括 CMP): 当运算时产生了 借位 时(无符号数溢出),C = 0,否则 C = 1 。
- V(Overflow)标志: CPSR 的第 28 位是 V,溢出标志位;在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
ARM64约定
x0 ~ x7 分别会存放方法的前 8 个参数;如果参数个数超过了8个,多余的参数会存在栈上,新方法会通过栈来读取;
方法的返回值一般都在 x0 上;如果方法返回值是一个较大的数据结构时,结果会存在 x8 执行的地址上。
常见指令
MOV
将某一寄存器的值复制到另一寄存器(只能用于寄存器与寄存器或者寄存器与常量之间传值,不能用于内存地址)
mov x1, x0 ; 将寄存器 x0 的值复制到寄存器 x1 中
ADD
将某一寄存器的值和另一寄存器的值 相加 并将结果保存在另一寄存器中
add x0, x0, #1 ; 将寄存器 x0 的值和常量 1 相加后保存在寄存器 x0 中
add x0, x1, x2 ; 将寄存器 x1 和 x2 的值相加后保存到寄存器 x0 中
add x0, x1, [x2] ; 将寄存器 x1 的值加上寄存器 x2 的值作为地址,再取该内存地址的内容放入寄存器 x0 中
SUB
将某一寄存器的值和另一寄存器的值 相减 并将结果保存在另一寄存器中
sub x0, x1, x2 ; 将寄存器 x1 和 x2 的值相减后保存到寄存器 x0 中
MUL
将某一寄存器的值和另一个寄存器的值 相乘 并将结果保存在另一寄存器中
mul x0, x1, x2 ; 将寄存器 x1 和 x2 的值相乘后结果保存到寄存器 x0 中
SIDV, UDIV
(有符号数,对应 udiv: 无符号数)将某一寄存器的值和另一个寄存器的值 相除 并将结果保存在另一寄存器中
sdiv x0, x1, x2 ; 将寄存器 x1 和 x2 的值相除后结果保存到寄存器 x0 中
AND
将某一寄存器的值和另一寄存器的值 按位与 并将结果保存到另一寄存器中
and x0, x0, #0xf ; 将寄存器 x0 的值和常量 0xf 按位与后保存到寄存器 x0 中
ORR
将某一寄存器的值和另一寄存器的值 按位或 并将结果保存到另一寄存器中
orr x0, x0, #9 ; 将寄存器 x0 的值和常量 9 按位或后保存到寄存器 x0 中
EOR
将某一寄存器的值和另一寄存器的值 按位异或 并将结果保存到另一寄存器中
eor x0, x0, #0xf ; 将寄存器 x0 的值和常量 0xf 按位异或后保存到寄存器 x0 中
STR
将寄存器中的值写入到内存中
str w9, [sp, #0x8] ; 将寄存器 w9 中的值保存到栈内存 [sp + 0x8] 处
LDR
将内存中的值读取到寄存器中
ldr x0, [x1] ; 将寄存器 x1 的值作为地址,取该内存地址的值放入寄存器 x0 中
ldr w8, [sp, #0x8] ; 将栈内存 [sp + 0x8] 处的值读取到 w8 寄存器中
ldr x0, [x1, #4]! ; 将寄存器 x1 的值加上 4 作为内存地址, 取该内存地址的值放入寄存器 x0 中, 然后将寄存器 x1 的值加上 4 放入寄存器 x1 中
ldr x0, [x1], #4 ; 将寄存器 x1 的值作为内存地址,取内该存地址的值放入寄存器 x0 中, 再将寄存器 x1 的值加上 4 放入寄存器 x1 中
ldr x0, [x1, x2] ; 将寄存器 x1 和寄存器 x2 的值相加作为地址,取该内存地址的值放入寄存器 x0 中
STP
入栈指令(str 的变种指令,可以同时操作两个寄存器)
stp x29, x30, [sp, #0x10] ; 将 x29, x30 的值存入 sp 偏移 16 个字节的位置
LDP
出栈指令(ldr 的变种指令,可以同时操作两个寄存器)
ldp x29, x30, [sp, #0x10] ; 将 sp 偏移 16 个字节的值取出来,存入寄存器 x29 和寄存器 x30
SCVTF, FCVTZS
SCVTF 带符号定点数转换为浮点数
FCVTZS 浮点数 转化为 定点数
scvtf d1, w0 ; 将寄存器 w0 的值(顶点数,转化成 浮点数) 保存到 向量寄存器/浮点寄存器 d1 中
fcvtzs w0, s0 ; 将向量寄存器 s0 的值(浮点数,转换成 定点数)保存到寄存器 w0 中
比较指令
- CBZ: 和 0 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)
- CBNZ: 和非 0 比较(Compare),如果结果非零(Non Zero)就转移(只能跳到后面的指令)
- CMP: 比较指令,相当于 subs,影响程序状态寄存器CPSR
位移指令
- LSL: 逻辑左移
- LSR:逻辑右移
- ASR:算数右移
- ROR:循环右移
跳转指令
B: 跳转到某地址(无返回), 不会改变 lr (x30) 寄存器的值;一般是本方法内的跳转,如 while 循环,if else 等
b LBB0_1 ; 直接跳转到标签 ‘LLB0_1’ 处开始执行
BL: 跳转到某地址(有返回),先将下一指令地址(即函数返回地址)保存到寄存器 lr (x30)中,再进行跳转 ;一般用于不同方法直接的调用
bl 0x100cfa754 ; 先将下一指令地址(‘0x100cfa754’ 函数调用后的返回地址)保存到寄存器 ‘lr’ 中,然后再调用 ‘0x100cfa754’ 函数
BLR: 跳转到 某寄存器 (的值)指向的地址(有返回),先将下一指令地址(即函数返回地址)保存到寄存器 lr (x30)中,再进行跳转
blr x20 ; 先将下一指令地址(‘x20’指向的函数调用后的返回地址)保存到寄存器 ‘lr’ 中,然后再调用 ‘x20’ 指向的函数
BR: 跳转到某寄存器(的值)指向的地址(无返回), 不会改变 lr (x30) 寄存器的值
RET: 子程序(函数调用)返回指令,返回地址已默认保存在寄存器 lr (x30) 中
问题1
有一c函数如下,其汇编代码主体如下:
void TestPushAndPop(){
printf("Push an Pop !");
}
sub sp, sp, #32 ; 更新栈顶寄存器的值,(可以看出:申请 32 字节占空间作为新用)
stp x29, x30, [sp, #16] ; 保存调用该函数前的栈顶寄存器的值和该函数结束返回后下一将执行指令地址值
add x29, sp, #16 ; 更新栈底寄存器的值,(可以看出:还剩余 16 字节空间给该函数用)
adrp x0, l_.str@PAGE ; 获取 ‘l_.str’ 标签所在的页的地址
add x0, x0, l_.str@PAGEOFF ; 获取 ‘l_.str’ 标签对应页地址的偏移
bl _printf ; 调用 ‘printf’ 函数进行打印
stur w0, [x29, #-4] ; 将 w0 寄存器的值('bl' 函数调用的返回值)保存到 [x29 - 4] 的内存地址中
ldp x29, x30, [sp, #16] ; 恢复调用该函数之前栈底寄存器的值
add sp, sp, #32 ; 恢复调用该函数之前栈顶寄存器的值
ret ; 返回
对与上面的汇编代码,分配了 32 自己空间,其中 16 字节是用作 入栈操作,剩下的 16 字节是用于存储临时变量的。
疑问: 例子函数命名是没有临时变量,为什么还会需要申请占空间?
解释: 虽然该函数没有临时变量,但是调用 printf 函数后,编译器自动会加上 该函数返回值 的处理,由于 arm64 规定了整数型返回值放在 x0 寄存器里,
因此会隐藏有一个局部变量 int return_value; 的声明在,该临时变量占用 4字节空间;又因为 arm64 下对于使用 sp 作为地址基址寻址的时候,
必须要 16byte-alignment(对齐),所以申请了 16字节空间作为临时变量使用。