ARMv8 汇编指令

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掩码,

比如:可用来关闭/打开本地处理器中断

msr daifset, #0xf  //disable daif
msr daifclr, #0xf  //enable 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 条件/比较跳转

指令说明
BB label  //无条件跳转PC ±128MB的范围
B.condB.cond lable //有条件跳转,cond的具体值见2.3章节
BLBL lable  //带返回值的跳转。BL将返回地址设置到LR(X30)寄存器中,保存的值为调用BL指令的当前PC值+4
BRBR Xn  //跳转到寄存器指定的地址
BLRBLR 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、异常处理指令

指令描述
SVCSVC #imm  //系统调用指令, 允许应用程序通过SVC指令自陷入到操作系统OS中,进入EL1
HVCHVC #imm //虚拟化系统调用指令,允许主机OS通过HVC指令自陷入到hypervisor中,进入EL2
SMCSMC #imm //安全监控系统调用指令,允许主机OS 或者 Hypervisor程序 通过SMC自陷入到安全监控管理程序,进入EL3

操作系统一般不直接使用imm参数传递系统调用号 (system call number), 而是通过通用寄存器来传递。CPU执行SVC指令后,进入异常处理,在异常处理中需要把异常触发的现场保存到内核栈里。操作系统利用这个特性,使用一个通用寄存器来传递系统调用号。
 

6、内存屏障指令

指令描述
DMB数据内存屏障,确保在执行新的存储器访问前所有的存储访问都已经完成
DSB数据同步屏障,确保在下一个指令执行前所有的存储器访问都已经完成
ISB指令同步屏障,清空流水线,确保在执行新的指令前,旧的指令已经完成
LDARLDAR指令后面的读写内存指令必须在LDAR指令之后才能执行
STLR所有加载和存储指令必须在STLR之前完成

7、ADR、ADRP与LDR

ADR、ADRP都是加载当前PC相对地址,ADRP加载当前PC值一定范围标签地址,这个地址与标签所在的地址按照4KB对齐。

adrp x0, init_page_dir  // 加载init_page_dir 标签地址

ldr x0, =init_page_dir   // 加载init_page_dir 标签的链接地址

例子和测试结果:

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值