aarch64体系结构与编程1--常用汇编指令
Opened this issue · 0 comments
加载指令
ldr指令寻址之1:地址偏移模式(unsigned offset)
地址偏移模式常常使用寄存器的值来表示一个地址,或者基于寄存器的值做一些偏移来计算出内存地址,并且把这个内存地址的值加载到通用寄存器中。偏移量可以是正数,也可以是负数。
LDR Xd, [Xn, $offset]
首先在Xn寄存器的内容上加一个offset偏移量后作为内存地址,加载此
地址的内容到Xd寄存器。
ldr指令寻址之2:变基模式
变基模式主要有两种。
- 前变基模式(pre-index模式),先更新偏移地址然后再访问内存
- 后变基模式(post-index模式),先访问内存地址然后再更新偏移地址
LDR X0, [X1, #8]! //前变基模式。先更新X1的值为X1+8,然后以新的X1值为地址,加载内存的值到X0
LDR X0, [X1], #8 //后变基模式。以X1的值为地址,加载该内存地址的值到X0,然后再更新X1寄存器为X1+8
ldr指令寻址之3:标签(literal)
ldr指令有一个加载标签的模式. 读取 (PC值 + label offset) 这个地址的值
ldr伪指令
- 指令:每一条指令都对应一种CPU操作。
- 伪指令:对编译器发出的命令,它是在对源程序汇编期间由汇编程序处理的操作,它们可以完成如处理器选择、定义程序模式、定义数据、分配存储区、指示程序结束等功能,总之,可以分解为几条指令的集合。
- ldr指令既可以是大范围的地址读取伪指令,也可以内存访问指令。当它的第二个参数前面有“=”时,表示伪指令,否则表示内存访问指令。
ldr x6, MY_LABEL //内存访问指令
ldr x7, =MY_LABEL //ldr伪指令
dr伪指令没有立即数范围的限制
加载指令4 mov指令
- 16为立即数
- 或者16位立即数左移16, 32, 48位
加载指令5 多字节的加载和存储指令ldp和stp指令
- 在A32指令集中提供LDM和STM指令来实现多字节内存加载和存储,到了A64指令集,不再提供LDM和STM指令,而是采用LDP和STP指令
- ldp和stp可以一条指令加载和存储16个字节,效率提高一倍
LDP X3, X7, [X0] //以X0的值为地址,加载此地址的值到X3寄存器,以X0+8为地址,
加载此地址的值到X7寄存器
STP X1, X2, [X4] //存储X1的值到地址为X4的内存中,然后存储X2的值到地址为X4+8
的内存中。
存储指令之变种
根据数据大小,例如1个字节,2个字节,有符号数等ldr和str指令有如下变种
指令 | 说明 |
---|---|
LDR | 数据加载指令 |
LDRSW | 有符号的数据加载指令,大小为字(word) |
LDRB | 数据加载指令,大小为字节 |
LDRSB | 有符号的加载指令,大小为字节 |
LDRH | 数据加载指令,大小为半字(HalfWord) |
LDRSH | 有符号的数据加载指令,大小为半字(HalfWord) |
STRB | 数据存储指令, 大小为字节 |
STRH | 数据存储指令, 大小为半字 |
注意:访问和存储4个字节和8个字节都是用ldr和str,只不过目标寄存器使用wn或者xn。
算术和移位指令
ADD加法指令
➢ 普通的加法指令add
➢ adds指令 – 影响条件标志位(进位)
✓ 1. 使用寄存器的加法
✓ 2. 使用立即数的加法
✓ 3. 使用移位操作的加法
主要影响C标志位
sub减法指令
- 普通的减法指令sub
- subs指令 - 影响条件标志位(C标志位)
带进位的加法指令
adc指令
Rd = Rn + Rm + C where R is either w or x.
带进位的减法指令
sbc指令
Rd = Rn - Rm - 1 + C
cmp指令
比较两个数的大小,内部使用subs指令来实现,影响C标志位。
等同于:
SUBS XZR, <Xn>, # <imm>
1.subs指令,s表示计算结果会影响 NCVZ标志位
2.subs xd,x1,x2的话,计算公式 xd= x1 + NOT(x2) +1
3.把xd,写成xzr,目的就是不要 xd的结果,只要 NCZV的 结果
cmp x1, x2
当x1 >= x2时, C=1
当x1 < x2 时, C=0
移位操作
- lsl:逻辑左移指令
- lsr:逻辑右移指令
- asr:算术右移
- ror:循环右移
注意:
- 逻辑左移 = 算术左移, arm64没有单独设置一个算术左移的指令, 最高位会被抛弃
- 逻辑右移和算术右移需要考虑 符号问题。
例如: 1010101010, 右移的话:
逻辑右移一位:[0]101010101 (最高一位,永远补0)
算数右移一位:[1]101010101 (算数右移,左边添加的数和符号有关)
按位与操作
- and:与 操作
- ands:带条件标志位的与操作,影响Z标志位
按位与操作
AND Xd, Xn
指令结果: Xd = Xd & Xn
按位或/异或操作
- orr: 或操作
- eor: 异或操作
按位或操作
ORR Xd, Xn
指令结果: Xd = Xd | Xn
按位异或操作
EOR Xd, Xn
指令结果: Xd = Xd ^ Xn
异或的特点: - 0^0=0,0^1=1, 0异或任何数=任何数
- 1^0=1,1^1=0, 1异或任何数-任何数取反
- 任何数异或自己=把自己置0
异或的几个小妙用
(1)使某些特定的位翻转
例如想把10100001的第2位和第3位翻转,则可以将该数与00000110进行按位异或运算。
10100001^00000110 = 10100111
(2)交换两个数.
例如交换两个整数a=10100001,b=00000110的值,可通过下列语句实现:
a = a^b;//a=10100111
b = b^a;//b=10100001
a = a^b;//a=00000110
(3)在汇编里让变量设置为0
eor x0, x0
(4)判断两个是否相等
return ((a ^ b) == 0)
按位清除操作
- bic:位清零指令
位段插入操作
位段提取操作
- UBFX: 无符号数的位段提取指令
- SBFX:有符号数的位段提取指令
SBFX Xd, Xn, #lsb, #width ;
从Xn寄存器提取位段,位段从第lsb位开始,位宽为width,然后结果写入到Xd寄存器最低比特位中。
注意: - UBFX: 其他比特位是填充0
- SBFX:其他比特位全是填充f
零计数指令clz
clz:计算最高为1的比特位前面有几个0
X1 = 0x0fffffffffffffff
clz x2, x1
结果为4
比较和跳转指令
比较指令
条件选择指令
基本跳转指令
b:跳转指令, 无条件的跳转指令,不返回。
跳转范围:PC +/- 128MB
有条件的跳转指令 b.cnd
- b.cnd:有条件的跳转指令, 不返回。
- cnd为条件操作后缀
- 跳转范围:PC +/- 1MB
bx指令
bx:跳转到寄存器指定的地址处,不返回。
带返回地址的跳转指令
- bl:带返回地址(PC+4 => x30),适用于call 子函数
- 返回地址:保存到x30中,=>保存的是父函数的PC+4
- 跳转范围:PC +/- 128MB
blx指令
- blx:跳转到寄存器指定的地址处,可以返回
- 返回地址:保存到x30中,=>保存的是父函数的PC+4
ret指令
ret:从子函数返回。
eret指令
eret:从当前的异常模式返回。
通常可以实现模式切换,例如EL1切换到EL0
在遇到嵌套调用bl时候,需要在父函数里,先把x30寄存器保存到一个临时寄存器。在父函数ret返回时,从临时寄存器中恢复x30寄存器的值,再ret返回
比较并跳转指令
- cbz:比较xt寄存器是否为0,为0则跳转到label标签处,跳转范围为+/- 1MB
- cbnz:比较xt寄存器是否为0,不为0则跳转到label标签处,跳转范围为+/- 1MB
- tbz:测试寄存器中某个比特位是否为0,为0则跳转, 跳转范围为+/- 32KB
- tbnz:测试寄存器中某个比特位是否为0,不为0则跳转, 跳转范围为+/- 32KB
其他重要指令
PC相对地址加载指令
- adr指令:加载PC相对地址的label的地址,范围为+/- 1MB
- adrp指令:加载PC相对地址的label的地址,它只加载label所属的4KB对齐的地址,范围为 +/- 4GB
adr x0, label => 读取label的地址,相对PC的
adrp x0, lable => 读取label所在的4KB的地址,相对PC的
读取详细的地址:add x0, x0, #:lo12:lable
内存独占加载和存储指令
- ldxr指令:内存独占加载指令。从内存中以 独占exclusive的方式加载内存的地址到通用寄存器。
- stxr指令:内存独占存储指令。
ldxr是加载内存,不过它会通过独占监视器来监视这个内存的访问,监视器会把这个内存地址标记为独占访问,保证其独占的方式来访问
Stxr是有条件地存储内存,刚才ldxr标记的内存地址被独占的方式 存储了
ldxr xd,[xn | sp] => 独占的读取xn或者sp地址的内容到xd寄存器
stxr wd,xt,[xn | sp] => 独占地方式把xt的内容写入到xn或者sp地址
Wd为0表示 成功
Wd为1表示不成功 - ldxr指令和stxr指令通常需要配对使用
- Linux内核常常用来实现 atomic的访问,例如atomic_write(), atomic_set_bit()
- spinlock机制可以简单地使用ldxr和stxr指令来实现
异常处理指令
指令 | 描述 |
---|---|
SVC | 系统调用指令 svc #imm运行应用程序通过SVC指令来自陷到操作系统里, 通常会进入到EL1异常 |
HVC | 虚拟化系统调用指令 |
SMC | 安全监控系统调用指令 |
系统寄存器访问指令
指令 | 描述 |
---|---|
MRS | 读取系统寄存器到通用寄存器 |
MSR | 更新系统寄存器 |
内存屏障指令
(1)数据存储屏障(Data Memory Barrier,DMB) 指令
保证的是内存屏障前后的内存访问指令的执行顺序,不会保证内存访问指令在内存屏障指令之前必须完成
(2)数据同步屏障(Data synchronization Barrier,DSB)指令
任何指令都要等待DSB前面的存储访问完成
(3)指令同步屏障(Instruction synchronization Barrier,ISB)指令
冲洗流水线(Flush Pipeline)和预取buffers后,才会从高速缓存或者内存中预取ISB指令之后的指令
DMB和DSB指令还可以带参数,来指定内存屏障指令的顺序以及共享属性等信息