A64指令集
本篇只对A64指令集进行总结。
1.A64汇编指令简介
- A64指令只能运行在AARCH64环境中
- 所有A64汇编指令都是32bit位宽
- A64支持全部大写或全部小写的书写方式
- 寄存器命名:
A64指令分类
内存加载和存储指令
多字节内存加载和存储
算术和移位指令
移位操作
位操作指令
条件操作
跳转指令
独占访存指令
内存屏障指令
异常处理指令
系统寄存器访问指令
2.存储和加载指令
ldr指令和str指令:
ARMv8、ARMv9架构也是基于加载和存储架构。
加载和存储指令的格式:
- LDR 目标寄存器, <存储器地址> //把存储器地址的数据加载到目标寄存器中
- STR 源寄存器, <存储器地址> //把源寄存器的值存储到存储器中
关于“<存储器地址>”的表示,有很多种方式。
[1].ldr指令寻址方式1:地址偏移模式(unsigned offset)
地址偏移模式,通常使用寄存器的值来表示一个地址,或者基于寄存器的值做一些偏移来计算出内存地址,并且把这个内存地址的值加载到通用寄存器中。偏移量可以是正数,也可以是负数。
ldr Xd, [Xn, $offset]
首先在Xn寄存器的内容上加一个offset偏移量后作为内存地址,加载此地址的内容到Xd寄存器。
实践:
1 首先使用mov指令把 0x80000加载到x1寄存器, 使用mov指令把16数值加载到x3寄存器
2. 使用ldr指令读取0x80000地址的值。
3. 使用ldr指令来读取0x80008地址的值。
4. 使用ldr指令来读取 (x1 + x3)寄存器的值。
5. 使用ldr指令来读取(x1+ x3<<3) 地址的值
将代码写成如下这样:
.global ldr_test
ldr_test:
mov x1, 0x80000
mov x3, 16
//使用ldr指令读取0x80000地址的值
ldr x0, [x1]
//使用ldr指令来读取0x80008地址的值。
ldr x0, [x1, #8]
//使用ldr指令来读取 (x1 + x3)寄存器的值。
ldr x0, [x1, x3]
//使用ldr指令来读取(x1+ x3<<3) 地址的值
ldr x0, [x1, x3,lsl #3]
需要注意的是这里的lsl 移位指令,后面的数字只能是0或3
[2].ldr指令寻址方式2:变基模式
变基模式主要有两种:
前变基模式(pre-index模式),先更新偏移地址然后再访问内存。
后变基模式(post-index模式),先访问内存地址然后再更新偏移地址。
LDR X0, [X1, #8]! //前变基模式。先更新X1的值为X1+8,然后以新的X1值为地址,加载内存的值到X0
LDR X0, [X1], #8 //后变基模式。以X1的值为地址,加载该内存地址的值到X0,然后再更新X1寄存器为X1+8
[3].ldr指令寻址方式3:标签模式(literal)
标签模式,也叫pc相对寻址。本质是:读取(PC值+ label offset) 这个地址的值**
ldr ,
<label>
的位置必须在当前指令地址的1MB范围之内。超出这个范围,编译器会报错。
例1:
其中x6和x7寄存器的值分别是多少?
#define MY_LABEL 0x20
ldr x6, MY_LABEL
ldr x7, =MY_LABEL
区别在于“=”.
- 前者属于pc位置相对寻址,将(pc+MY_LABEL)地址处的值加载都x6。
- 后者是一个伪指令。表示将MY_LABEL的值加载到x7。
例2:
其中x6,x7, x8寄存器的值分别是多少?
data:
.word 0x40
ldr x6,data //
ldr x7,=data
ldr x8, [x7]
标签data定义了一个数据,data实际上代表的是一个地址,而该地址处数据的值是0x40。
第一句,是普通的加载指令。读取到的是data标签位置的值,0x40。
第二句,是伪指令。读取的是标签本身代表的值,也就是data对应的地址。它没有地址范围的限制。
第三局,普通加载指令。读取的是[x7]代表的地址处的值,因此结果是0x40。
[4]加载指令4:mov
mov指令是最简单和最原始的指令。
Mov指令加载有限制:
- 16位立即数
- 或者16位立即数 左移 16,32,48位
[5]多字节加载(ldp)\存储(stp)指令
- 在A32指令集中通过ldm和stm指令来加载和存储多个字节内存
- 而在A64指令集中,使用ldp和stp作为多字节内存加载和存储
- ldp和stp 一条指令可以加载和存储16个字节
LDP X3, X7, [X0] //以X0的值为地址,加载此地址的值到X3寄存器,以X0+8为地址,加载此地址的值到X7寄存器
STP X1, X2, [X4] //存储X1的值到地址为X4的内存中,然后存储X2的值到地址为X4+8的内存中。
[6]加载和存储指令练习
1.加载一个很大的数值到通用寄存器,例如0xffff_000_ffff_ffff
//伪指令允许加载很大的数。mov有限制。
ldr x1, =0xffff_000_ffff_ffff
- 加载一个寄存器的值,例如sctrl_el1寄存器。
//mov只支持16位数局,左移0,16,32,48 位。对于超出16bit任意数无能为了。
//mov x1, (1 << 0) | (1 << 2) | (1<< 20) | (1<< 40) | (1<<55)
ldr x1, = (1 << 0) | (1 << 2) | (1<< 20) | (1<< 40) | (1<<55)
- 下面的代码,x0和x1分别是多少?
string1:
.string "Booting at EL“
ldr x0, string1
ldr x1, =string1
x0 为string1变量的值代表的地址处的值,即为字符串的ASCII码值。
x1为string1变量的值,本身是个地址。
[7]加载和存储指令的变种
根据单字节,半字,字,有符号数和无符号数,有ldr和str有如下变种。
注意:访问和存储4个字节和8个字节都是用ldr和str,只不过目标寄存器使用wn或者xn。
3.算数和移位指令
条件操作码
在处理器状态PSTATE中有NZCV特殊寄存器表示4个状态码。
3.1 加法指令
[1] 普通加法指令add
1.使用立即数的加法指令
add <Xd|SP>, <Xn|SP>, #{, shift}
- Xd|SP, 目标寄存器,可以是通用寄存器或栈地址寄存器。
- Xn|SP,源寄存器,可以是通用寄存器或栈地址寄存器。
- imm:立即数。是一个无符号立即数,范围在0~4095.
- shift, 可选项,用来表示算术左移操作。只能是LSL 0或LSL 12.
add x0, x1, #1
add x0, x1, #1, LSL #12
下面是错误做法,编译器会报错。
add x0, x1, #4096
add x0, x1, #1, LSL #1
2. 使用寄存器的加法
add <Xd|SP>, <Xn|SP>, {, {#{amount}}}
这条指令是把Rn寄存器做一些扩展,例如左移操作,然后再加上 <Xn|SP>寄存器的值,把结果写道<Xd|SP>寄存器
- Xd|SP, 目标寄存器,可以是通用寄存器或栈地址寄存器。
- Xn|SP,源寄存器,可以是通用寄存器或栈地址寄存器。
- R:表示第2个操作数是32位还是64位通用操作器。
3. 使用移位操作的加法