1、加载,存储指令
1.1 MOV
MOV <Xd|SP>, <Xn|SP>
MOV <Xd|SP>, #<imm16>
- 常用于寄存器之间的搬移和立即数搬移,
- 仅仅支持imm16, 0-4096大小范围的立即数操作
1.2 MRS,MSR访问系统寄存器
MRS: 状态寄存器 --> 通用寄存器的传送指令。
MSR: 通用寄存器 --> 状态寄存器的传送指令。
注意:在ARMv7里通过CP15协处理器方位系统寄存器
mrs x10, sctlr_el1 // 读取sctlr_el1系统控制寄存器属性配置
msr sctlr_el1 , x10 // 设置sctlr_el1
还可以访问PSTATE寄存器一些重要的相关字段。
特殊的系统寄存器 | 说明 |
CurrentEL | 当前系统的异常等级 |
DAIF | 获取和设置PSTATE中的DAIF掩码, 比如:可用来关闭/打开本地处理器中断 |
NACV | 获取和设置PSTATE中的条件掩码 |
PAN | 获取和设置PSTATE中的PAN字段 |
SPSel | 获取和设置当前SP寄存器 |
UAO | 获取和设置PSTATE中的UAO字段 |
1.3 LDR, STR
LDR Xt, [Xn] //以Xn寄存器中的内容作为内存地址,加载此内容地址的内容到Xt目标寄存器中
LDR Xt, =<label> // 把label标记的地址加载到Xt寄存器中
STR Xt, [Xn] //把Xt寄存器中的内容存储到Xn寄存器的内容地址中
#define MY_PC_OFFSET 0x100
my_data:
.quad 0x09
// 1. 测试ldr地址偏移模式
mov x1, 0x80000
mov x3, 16
/* 读取0x80000地址的值到x0寄存器*/
ldr x0, [x1]
ldr x1, MY_PC_OFFSET //读取当前PC+0x100的地址的内容到x1中
ldr x4, =my_data // 把my_data对应的地址加载到x4寄存器中
ldr x5, [x4] //加载x4内存地址对应的值到x5中
得到的结果:
0x80000对应的memory空间:8x8 = 64bit的空间
1.4 LDXR, STXR独占内存访问指令
LDXR <Xt>, [<Xn|SP>{,#0}]
STXR <Ws>, <Xt>, [<Xn|SP>{,#0}]
<Ws>: Is the 32-bit name of the general-purpose register into which the status result of the store exclusive is written,表示写的状态是成功还是失败0 If the operation updates memory.1 If the operation fails to update memory.<Xt>:Is the general-purpose register to be transferred.<Xn|SP>:Is the general-purpose base register or stack pointer.
独占内存访问指令,LDXR指令尝试在内存总线中申请一个独占访问的锁,然后访问一个内存地址。STXR指令会往刚才LDXR指令已经申请独占访问的内存地址写入新内容。LDXR和STXR指令通常组合使用完成一些同步操作,比如linux内核的自旋锁。
另外还有LDXP,STXP也是支持多字节独占访问的指令;还有LDAR, STLR,内存屏障原语操作
1.4 LDP, STP 多字节内存加载和存储指令
LDP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]
- 说明:LDP 以Xn/SP 寄存器的值为基地址,然后读取Xn|SP寄存器的值+imm地址的值到Xt1寄存器,读取Xn|SP寄存器的值+imm+8地址的值到Xt2寄存器,
STP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]
- 说明:STP 以Xn/SP 寄存器的值为基地址,然后把Xt1寄存器的内容存储到 【Xn|SP+imm】处,然后把Xt2寄存器的内容存储到 【Xn|SP+imm+8】处
LDP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>] !
- 说明:前变基模式,先计算Xn 寄存器值+imm,并存储到Xn寄存器中,然后以Xn寄存器最新值作为基地址,读取【Xn】的值到Xt1寄存器,读取【Xn+8】的值到Xt2
LDP <Xt1>, <Xt2>, [<Xn|SP>], #<imm>
- 说明:后变基模式,以Xn寄存器的值作为基地址,读取【Xn】的值到Xt1寄存器,读取【Xn+8】的值到Xt2,最后更新Xn基地址为 【Xn 寄存器值+imm】
STP前变基:STP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>] !
STP后变基:STP <Xt1>, <Xt2>, <Xn|SP>, #<imm>
说明:
<imm>:基地址的偏移量,7bit;
//void *__memset_16bytes(void *s, unsigned long val, unsigned long count);
// 说明: 函数的 参数1 由通用寄存器x0保存,参数2由寄存器x1保存。。。。。。
__memset_16bytes:
mov x4, #0
1:
stp x1, x1, [x0], #16 //把第二个参数val的值由x1保存到第一个参数s的指针对应的地址x0,以及x0+8对应的两个字节,然后x0基地址+16
add x4, x4, #16 //计数已经更新了几个字节
cmp x4, x2 //比较是否已经更新完毕,x2保存 count的值
bne 1b //不相等,则跳转到1继续执行
ret
说明:在函数调用过程中,如果传递的参数少于或者等于8个,那么使用X0-X7通用寄存器传递,如果多于8个,则需要使用栈stack传递
2、算术指令
2.1 加法指令
2.1.1 ADD
ADD <Xd|SP>,<Xn|SP>,#<imm12>{,<shift>#<amount>}
ADD <Xd|SP>,<Xn|SP>,#<R><m>{,<extend>{#<amount>}}
- 说明:
<Xd>:目标寄存器
<Xn>:源寄存器
<imm>:为无符号12bit位宽,所以只能在0-4095范围设定;
<shift>:只能LSL #0 , 或者 LSL #12, 即amount =0, 或者 12
<extend>:可为UXTB,UXTH,UXTW,LSL|UXTX,SXTB,SXTH,SXTW,SXTX操作,这里amount取值范围为0-4,3bit位宽
- 注意:
特别注意在ARM指令中立即数和amount的值都是有限制条件的,一定要检查ARM指令手册
- 例子:
mov x0, #0
mov x3, #0
mov x1, #1111
mov x2, #2222
add x0, x1,#1234 // 把x1的寄存器的值加上1234, 相加的结果保存到x0
add x3, x2, #1, LSL 12//把x2的寄存器的值加上1左移12后的值,保存到x3
mov x6, #0
mov x7, #0
mov x8, #0
mov x4, #0x1111
mov x5, #0x108a
add x6, x4, x5, UXTB //对X5的低8bit的数据进行无符号扩展后,x5结果为0x8A,但是不影响x4的值, 所以x6=x4(0x1111)+0x8a = 0x119b
add x7, x4, x5, UXTH //对X5的16bit的数据进行无符号扩展后,x5结果为0x108A,但是不影响x4的值, 所以x6=x4(0x1111)+0x1018a = 0x209b
mov x4, 0
add x8, x4, x5, SXTB 对x5的值进行低8bit有符号扩展后,x5结果=0xFFFF FFFF FFFF FF8A,然后再加上x4的值,最终结果x8 = 0xFFFF FFFF FFFF FF8A
运行完以后的各个寄存器值为:
2.1.3 ADDS
ADDS 指令是ADD的变种,唯一区别是指令执行结果会影响PSTATE寄存器的N、Z、C、V标志位,比如当计算结果发生无符号数溢出时,C=1
mov x1, 0xffffffffffffffff
adds x0, x1, #2
mrs x2, nzcv // mrs状态寄存器 --> 通用寄存器传递,x1的值加上2一定触发无符号数溢出,导致x2=0x20000000,即nzcv寄存器的第29bit的C字段为1
2.1.3 ADC
ADC <Xd>, <Xn>, <Xm>
ADC是进位加法指令,最终计算结果影响PSTATE寄存器C标志位。 Xd寄存器的值等于 Xn寄存器的值加上C
mov x1, 0xffffffffffffffff
mov x2, #2
adc x0, x1, x2 //adc指令计算过程是0xffffffffffffffff + 2 + C,此时C=0,所以x0的计算结果为1
mrs x3, nzcv // x3=0x80000000
运行完以后的各个寄存器值为:
2.2 减法指令
2.2.1 SUB
SUB <Xd|SP>,<Xn|SP>, #<imm>{,<shift>#<amount>}
SUB <Xd|SP>,<Xn|SP>, #<R><m>{,<extend>{#<amount>}}
SUBS:与SUB唯一区别是指令执行结果会影响PSTATE寄存器标志位nzcv, SUBS等于以下操作: Xn+ NOT(Xm) + 1
SUBC: 进位减法指令, Xd = Xn + NOT(Xm) +C //如果Xn + NOT(Xm)溢出,则C标志改变
比如:以下SUBS执行完第二个x2对应值为1,按位取反后为0xFFFF FFFF FFFF FFFE,根据公式计算3+0xFFFF FFFF FFFF FFFE +1, 这个过程发生无符号数溢出,因此C标志=1,最终结果为2,所以x4=0x2000 0000
mov x0, 0
mov x1, 3
mov x2, 1
mov x3, 0
mov x4, 0
sub x3, x1, x2 //SUB执行 x1-x2 = 3-1,所以x3=2
mrs x4, nzcv //SUB执行结果不影响PSTATE的nzcv标志
subs x0, x1, x2 //SUBS执行 3-1的操作,x0=2, 可为什么会发生无符号溢出呢?
mrs x4, nzcv //SUBS执行完第二个x2对应值为1,按位取反后为0xFFFF FFFF FFFF FFFE,根据公式计算3+0xFFFF FFFF FFFF FFFE +1, 这个过程发生无符号数溢出,因此C标志=1,最终结果为2,所以x4=0x2000 0000
运行完以后的各个寄存器值为:
2.3 CMP指令
CMP <Xn|SP>, #<imm> {, <shift>}
CMP <Xn|SP>, <R><m>{{,<extend>{#<amount>}}
当Xn 小于 Xm时,不产生无符号数溢出,C标志为0;反之,C标志为1
CMP指令相当于:SUBS XZR, <Xn|SP>, <R><m>
即:两个数相减 = Xn + “ Xm反码+1” : Xn + NOT(Xm) +1
CMP通常和跳转指令(B),条件操作后缀搭配使用。
以下是跳转指令-B的条件标志:
mov x1, 3
mov x2, 2
1:
cmp x1, x2 //比较x1,x2大小
b.ls 1b // 当b.ls为判断条件,表示无符号数 小于或等于,即 x1 <= x2时,就跳转
// 但是如果为b.cs判断条件时,表示无符号数 小于或等于大于,即x1>x2时,就跳转
2.4 位移指令
LSL:逻辑左移,
LSR:逻辑右移
ASR:算术右移,最高位按照符号进行扩展。与LSR的区别在于是否考虑符号问题
ROR:循环右移
比如:对于二进制数:1010 1010 10,
LSR 逻辑右移1位,变成[0]1010 1010 1 ,即在最高位补0;
而ASR算术右移,则变成[1]1010 1010 1, 即最高位按照原始数的符号进行扩展
ldr w1, =0x8000008a
asr w2, w1, 1 //算术右移1位,结果保存到w2中
lsr w3, w1, 1 //逻辑右移1位,结果保存到w3中
运行完以后的各个寄存器值为:w1为x1的低32bit 寄存器段
2.5 位操作指令
AND: 按位 与操作, 按位进行与操作,同1异0(即,“同”为1)
ANDS: 带条件标志位的与操作,影响 Z标志位
ORR:或操作指令,按位进行或操作,有1则1
EOR:异或操作,按位进行异或操作,同0异1(即,“异”为1)
BIC :位清除操作
CLZ:计算为1的最高位前面有几个为 0 的位
例子:
mov x1, 0x3
mov x2, 0x1
ands x3, x1, x2 //对0x3和0做 “与”操作,结果保存到x3
mrs x0, nzcv //当“与”操作结果为0时,Z标志位被置位
运行完以后的各个寄存器值为:
2.6 位段操作指令
BFI:位段插入
BFI <Xd>, <Xn>, #<lsb>, #<width>
说明:通过bfi操作, Xn寄存器中的Bit[0, width]替换Xd中的Bit[lsb, lsb+width] , 常用于设置寄存器的字段。
UBFX: 位段提取(无符号)
UBFX <Xd>, <Xn>, #<lsb>, #<width>
说明:通过UBFX操作, 提取Xn寄存器中的Bit[lsb, lsb+width-1], 然后保存到Xd寄存器中。
下面操作实现 把寄存器X0的Bit[7,4]替换为0x5
mov x0, #0
mov x1, #0x5
bfi x0, x1, #4, #4 //往寄存器A的Bit[7,4]字段设置0x5
运行完以后的各个寄存器值为:bfi把x1寄存器的Bit[3,0]设置为X0寄存器中的Bit[7,4],
3、比较指令
3.1 CMN
CMN <Xd>, #imm{, <shift>}
CMN <Xd>, <R><m> {, <extend> {#<amount>}}
CMN与CMP不同的地方是,CMN用来将一个数与另外一个数的相反数进行比较。
例子:
mov x1, 2
mov x2, -2
1:
cmn x1, x2 //x2寄存器的值为-2, 其相反数为2, 所以cmn比较下来是相等的
b.eq 1b //条件成立,跳转到1处
3.2 CSEL
CSEL <Xd>, <Xn>, <Xm>, <cond>
CSEL指令的作用是判断cond是否为真, 如果为真,则返回Xn; 否则, 返回Xm, 把Xm的值写入Xd。 CSEL通常和CMP搭配使用。cond的值见2.3章节
例子:
使用汇编实现以下c语言函数:
unsigned long csel_test(unsigned long a, unsigned long b)
{
if (a >= b)
return b+2;
else
return b-1;
}
汇编实现:
csel_test:
cmp x0, x1 // x0为第一个形参, x1为第二个形参
add x2, x1, 2
sub x3, x1, 1
csel x0, x2, x3, ge //使用cond=GE这个条件判断码,它会根据CMP的结果进行判断
ret
如果函数执行 csel_test(2,5);
在cmp运行完后ge条件不成立,则返回x3的值到x0, 运行完以后的各个寄存器值为:
3.3 CSET
CSET <Xd>, <cond>
当cond为真时,设置Xd的寄存器为1, 否则为0
cset_test:
cmp x0, x1 //cmp运算结果会改变cond值hi
cset x0, hi //如果cond=hi成立,形参1 比 形参2 大,x0被置为1, 否则为0
ret
3.4 CSINC
CSINC <Xd>, <Xn>, <Xm>, <cond>
当cond为真时,返回Xn的值(到Xd),否则返回Xm的值+1 (到Xd)
4、跳转指令
4.1 条件/比较跳转
指令 | 说明 |
B | B label //无条件跳转PC ±128MB的范围 |
B.cond | B.cond lable //有条件跳转,cond的具体值见2.3章节 |
BL | BL lable //带返回值的跳转。BL将返回地址设置到LR(X30)寄存器中,保存的值为调用BL指令的当前PC值+4 |
BR | BR Xn //跳转到寄存器指定的地址 |
BLR | BLR Xn //跳转到寄存器指定地址,BLR将返回地址设置到LR(X30)中 |
CBZ | CBZ Xt, lable // 比较并跳转 判断Xt 的寄存器是否为0,若为0, 则跳转到label处 |
CBNZ | CBNZ Xt, lable // 比较并跳转 判断Xt 的寄存器是否不为0,若不为0, 则跳转到label处 |
TBZ | TBZ R<t>, #imm, lable // 比较并跳转 判断Rt寄存器中#imm是否为0,若为0,则跳转到Lable |
TBNZ | TBZ R<t>, #imm, lable // 比较并跳转 判断Rt寄存器中#imm是否不为0,若不为0,则跳转到Lable |
4.2 返回指令
RET:函数返回,其返回地址保存在LR(X30)寄存器 -Link Register
ERET: 从当前异常模式返回。会把SPSR内容恢复到PSTATE寄存器里,从ELR中获取跳转地址并返回到该地址。ERET可以实现处理器的模式切换,比如EL1 切换到 EL0。
下图1:ldr_test的LR(X30)为0x80268, 当ldr_test返回时,必须是这个LR地址值(即PC=LR地址),否则PC会从一个意想不到的地址执行,或者导致程序崩溃。
下图2:当调用到csel_test时,LR(X30)的值被更新为调用子函数BL指令的PC值+4, 即:0x802cc + 4 = 0x802d0(csel_test LR值)。 这个场景就是LR地址被修改了,会导致“ldr_test”无法正确返回
正确的写法是:在调用csel_test时,把ldr_test的LR的地址保存到一个通用寄存器,当csel_test返回时,再更新ldr_test的LR地址。LR地址是用来链接调用者和被调用者关系的寄存器。
.global ldr_test
ret_test:
cmp x0, x1 //csel_test LR地址会被更新为上一个调用者的PC值+4
add x2, x1, 2
sub x3, x1, 1
csel x0, x2, x3, ge //使用cond=GE这个条件判断码,它会根据CMP的结果进行判断
ret //LR地址并不会自动更新
.global ldr_test
ldr_test:
mov x10, x30 // 保存ldr_test的返回地址LR 到x10
mov x0, #0x2
mov x1, #0x5
bl ret_test // 调用ret_test
mov x30, x10 // 恢复ldr_test的返回地址
ret //根据LR的地址返回,并LR地址恢复上一级调用者PC地址
5、异常处理指令
指令 | 描述 |
SVC | SVC #imm //系统调用指令, 允许应用程序通过SVC指令自陷入到操作系统OS中,进入EL1 |
HVC | HVC #imm //虚拟化系统调用指令,允许主机OS通过HVC指令自陷入到hypervisor中,进入EL2 |
SMC | SMC #imm //安全监控系统调用指令,允许主机OS 或者 Hypervisor程序 通过SMC自陷入到安全监控管理程序,进入EL3 |
操作系统一般不直接使用imm参数传递系统调用号 (system call number), 而是通过通用寄存器来传递。CPU执行SVC指令后,进入异常处理,在异常处理中需要把异常触发的现场保存到内核栈里。操作系统利用这个特性,使用一个通用寄存器来传递系统调用号。
6、内存屏障指令
指令 | 描述 |
DMB | 数据内存屏障,确保在执行新的存储器访问前所有的存储访问都已经完成 |
DSB | 数据同步屏障,确保在下一个指令执行前所有的存储器访问都已经完成 |
ISB | 指令同步屏障,清空流水线,确保在执行新的指令前,旧的指令已经完成 |
LDAR | LDAR指令后面的读写内存指令必须在LDAR指令之后才能执行 |
STLR | 所有加载和存储指令必须在STLR之前完成 |
7、ADR、ADRP与LDR
ADR、ADRP都是加载当前PC相对地址,ADRP加载当前PC值一定范围标签地址,这个地址与标签所在的地址按照4KB对齐。文章来源:https://www.toymoban.com/news/detail-860138.html
adrp x0, init_page_dir // 加载init_page_dir 标签地址
ldr x0, =init_page_dir // 加载init_page_dir 标签的链接地址
例子和测试结果:文章来源地址https://www.toymoban.com/news/detail-860138.html
到了这里,关于ARMv8 汇编指令的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!