ARMv8指令集架构

系列上篇:ARMv8寄存器组

系统寄存器

数据处理或加载/存储指令不能直接使用系统寄存器。相反,需要将系统寄存器的内容读入寄存器X,对其进行操作,然后写回系统寄存器。有两个用于访问系统寄存器的专用指令:
MRS Xd, 将系统寄存器读入Xd.
MSR , Xn 写入Xn系统寄存器。
系统寄存器由名称指定,例如SCTLR_EL1:MRS X0, SCTLR_EL1 读SCTLR_EL1入X0.
系统寄存器名称以_ELx. 指定_ELx访问寄存器所需的最低权限。

数据处理汇编指令

算术和逻辑运算

逻辑和整数算术指令的基本格式如下图所示。
指令的组成部分如下:

  • operation定义了指令的功能。例如,ADD执行加法,AND执行逻辑与。可以在操作中添加S来设置标志。例如,ADD变成ADDS,这个S告诉处理器根据指令的结果更新ALU标志。
  • Destination是指令的目的地,它总是一个寄存器,指定了操作结果应放在的位置。大多数指令只有一个目的寄存器。一些指令有两个目的寄存器。当目的寄存器是W寄存器时,对应的X寄存器的高32位被置为0。
  • 操作数(operand) 1始终是寄存器。这是指令的第一个输入。
  • 操作数(operand) 2是一个寄存器或常量,它是指令的第二个输入。当操作数2是一个寄存器时,它可以包含一个shift。当操作数2是一个常量时,它被编码在指令本身内。这意味着可用常数的范围是有限的。

有一些特殊情况,例如MOV和MVN指令。MOV将一个常量或另一个寄存器的内容移动到目标寄存器中。MOV和MVN只需要一个输入操作数,可以是寄存器,也可以是常量,如下所示:
MOV X0, #1 将通用寄存器X0的值设为1。
MVN W0, W1将通用寄存器W0的值设为~W1。
image.png

浮点数

浮点操作遵循与整数数据处理指令相同的格式,并使用浮点寄存器。与整数数据处理指令一样,操作的大小决定了所使用的寄存器的大小。浮点指令的operation总是以F开头。例如下面这个指令设置半精度的H0 = H1 / H2:
FDIV H0, H1, H2
下面这个指令设置单精度的S0 = S1 + S2:
FADD S0, S1, S2
下面这个指令设置双精度的D0 = D1 - D2:
FSUB D0, D1, D2

使用FMOV复制寄存器之间的文字位模式。还有一些指令可以转换为最接近的表示,如下图所示:
image.png
图 1. EMOV 示例

在此示例中,假设X0包含值 2(正整数 2):X0 = 0x0000_0000_0000_0002
然后,执行以下序列:
FMOV D0, X0
SCVTF D1, X0
两条指令都“复制”X0到D寄存器中。然而,结果却截然不同:
D0 = 0x0000_0000_0000_0002 = 9.88131e-324
D1 = 0x4000_0000_0000_0002 = 2.0
复制FMOV的文字位模式,当解释为浮点值时,这是一个非常不同的值。将SCVTF值转换X0为最接近的浮点等效值。
同样,FCVTxx可用于将浮点值转换为其最接近的整数表示形式(即转换成2)。

位运算

有一组指令用于操作寄存器内的位。下图为示例:
image.png
BFI指令在寄存器中插入一个位域。在上图中,BFI从源寄存器(W0)中获取一个6-bit字段,并将其插入到目标寄存器的第9位。
UBFX提取一个位域。在上图中,UBFX从源寄存器的第18位获取一个7位字段,并将其放入目标寄存器中。
其他指令可以反转字节或位顺序,如下图所示:
image.png

加载和存储LDR/STR指令详解

image.png
LDR指令是把内存地址

中的数据加载到指定的通用寄存器中;STR指令是把通用寄存器中的数据存储到内存地址
中。
其中,有两个可选指令标志,sign和size

Size

Size表示允许处理数据长度,有X和W两种。X表示64位,W表示32位。
下面这条指令从地址中load 32位到W0: LDR W0, [

]
下面这条指令从地址中load 64位到X0: LDR X0, [
]
Size字段允许load子寄存器大小的数据量。
下面这条指令将W0的底部字节(B 8位)存储到地址:STRB W0, [
]
下面这条指令将W0的底部半字(H 16位)存储到地址:STRH W0, [
]
最后这条指令将X0的底部字(W 32位)存储到地址: STRW X0, [
]

Sign

当只加载部分寄存器值时,其余部分默认是零扩展。Sign表示按照加载值的最高位扩展
image.pngimage.png
如果地址0x8000的字节包含值0x1F,那么LDRSB X4的结果将是什么?
LDRSB执行一个带符号扩展到64位的byte load。load值的最高有效位将被复制以填充64位寄存器。load的值0x1F的最高位是空的。因此X4中的值为0x0000_0000_0000_001F。

寻址

A64的load/store寻址模式有以下几种:

基址寄存器模式

最简单的寻址形式是单个寄存器。基址寄存器是一个X寄存器,它包含被访问数据的完整的或绝对的虚拟地址,如下图所示,LDR指令把X1寄存器中的值作为内存地址,读出其中的数值到W0寄存器中。
image.png

偏移量寻址模式

如下图所示。X1包含基址,#12是基址的字节偏移量。访问的地址是X1 + 12。偏移量可以是一个常量,也可以是另一个寄存器。例如,这种类型的寻址可能用于结构体。只需维护一个指向结构体基地址的指针,便可通过不同的偏移量来选择不同的成员。
只能以立即数的形式提供offset
image.png
image.png

预索引(Pre-index)寻址模式:

索引模式一共有两种变体:预索引模式(pre-index),该变体在访问内存前先进行移位操作(修改基寄存器的值);另一种变体是后索引模式(post-index),该变体在访问内存后才修改基寄存器的值。在指令语法中,在方括号后边添加感叹号来表示预索引模式,
image.png

如下图所示。预索引寻址类似于偏移寻址,不同之处在于base pointer会随着指令的执行而更新。图中,先把X1中的内存地址加上偏移量12,存入X1,然后存入W0。指令完成后X1的值为X1 + 12。
image.png

后索引(Post-index)寻址模式

使用后索引寻址,从基指针中的地址加载值,然后更新指针。
如下图,LDR先把X1内存地址的值加载到W0,然后把内存地址偏移12,新的内存地址存入X1。后索引寻址对于出栈很有用。该指令从堆栈指针所指向的位置加载值,然后将堆栈指针移动到堆栈中的下一个完整位置。
image.png
image.png

load pair和store pair

除了单个寄存器的load和store,A64指令集还有load pair(LDP)和store pair(STP)指令。这些成对指令将两个寄存器从存储器中传入和传出。
第一条指令将[X0]加载到W3,并将[X0 + 4](按字长度增加)加载到W7:LDP W3, W7, [X0]。
load pair和store pair指令通常用于栈的压入和弹出。
第一条指令将X0和X1压入堆栈: STP X0, X1, [SP, #-16]!
第二条指令从堆栈中弹出X0和X1: LDP X0, X1, [SP], #16
记住,在AArch64中,堆栈指针SP必须是128位对齐的。

相对寻址

相对寻址以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。寻址方式基于当前PC,因此是地址无关操作
image.png
说明1:PC-relative modes寻址方式基于当前PC,因此是地址无关操作
说明2:A64指令集引入PC-relative modes寻址模式是因为在A64指令集中,PC不再是通用寄存器,不能直接访问。在ARMv7体系结构中,PC属于通用寄存器,可以直接将PC作为基址寄存器。PC-relative modes寻址方式达到了同样的效果
说明3:标号(label)本质上是一个数值,即符号地址(链接地址),相当于PC+编码偏移值=label(目标地址)(具体在编码中可体现),并且默认情况下,标号只在定义它的源文件中可见
说明4:根据不同的寻址模式,以及提供offset的不同方式,LDR / STR指令有不同的编码方式

Offset

根据不同的编码格式,偏移量有三种:
LDR/STR中的偏移量和其他指令中的立即数还不是完全一样
image.png

ARM汇编指令编码

指令语法格式

所有的ARM指令都是32 bits固定长度
一条ARM指令语法格式分为如下几个部分:
{cond}{S},,<shifter_operand>
其中,<>内的项是必须的,{}内的项是可选的,
opcode:指令助记符,如ADD、SUB、MOV等;
cond:条件码助记符,如EQ(0000)、NE(0001)、AL(1110)**(无条件执行)**等;
S:如果指令有S后缀,则该指令的操作会影响CPSR的值;
Rd:目标寄存器;
Rn:包含第一个源操作数的寄存器;
shifter_operand:表示第二个源操作数,可以为寄存器或立即数。例如:1-- 立即数 add r1,r2,#10 ; 2-- 寄存器 addeqs r1,r2,r3 @ r1=r2+r3 ; 3-- 寄存器移位 add r1,r2,r3,LSL #2 @ r1 = r2 +r3*4)

立即数编码格式

image.png

  • immed_8:8 位的常数
  • rotate_imm:4 位的循环右移值

举例:
image.png
上面两个立即数,编码后,前12位别是:
**1101 0100 0000 **
1000 1111 1111

从上面可以看出,并不是所有整数都是立即数。
立即数有一些比较明显的判断特征:
(1) 首先将数据转换成二进制形式。
(2) 如果1的个数大于8,则一定不是立即数。
(3) 如果最高和最低位的1之间有超过24个0,则一定不是立即数。
(4) 掐头去尾,分别去掉最高和最低位的1两侧最大偶数个0,如果剩下的位数仍然大于8,则一定不是立即数。
比如:mov r0, # 0x4FF @不是立即数报错Error: invalid constant (101) after fixup
第一步:0100 1111 1111
第二步:其中1的个数是9个,大于8个,判定不是立即数

所以,我们在ARM汇编中如何规避立即数这个问题呢? 其实可以使用ARM汇编LDR伪指令,例如直接把MOV指令变为, LDR R1,=0x12345678这样编译器就不会报错了。但这种方法也有弊端会增加开销和影响执行效率。同时ARM汇编中还有有效数的概念,比如 MOV R1,#0xFFFFFFFF 指令中 0xFFFFFFFF 不是立即数,但是是有效数,编译器最自动把原指令变换为 MVN R1,#0,也不会报错。有效数判定:原数是立即数或者原数反码是立即数

LDR / STR指令编码

image.png
因为每个指令的编码都不太一样,记住所有指令编码不太可能,也没有必要。看懂一种指令的编码,基本也就能触类旁通了。所以就以LDR/STR指令为例。
此处指令编码方式与寻址方式的对应关系如下,
虽然指令的编码方式不同,但是均使用STR / LDR助记符,从而减轻了程序员的负担

immediate指令编码

LDR(immediate)指令编码

Post-index编码

image.png

  1. size字段表示指令的32位形式还是64位形式,10->32位,11->64位
  2. imm9字段表示有符号立即数signed offset,取值范围为-256 ~ 255
例如:

image.pngimage.png

Pre-index编码

同理,不做赘述
image.png

Unsigned offset偏移量模式编码

image.png
对于32位形式,表示0 ~ 16380之间的4的倍数,此时imm12字段被编码为 / 4,默认值为0。positive imm12的取值范围为0 ~ 4095,以4为倍数,则可表示的取值范围为0 ~ 16380
对于64位形式,表示0 ~ 32760之间的8的倍数

例如:

image.pngimage.png

注意:

image.pngimage.png

对于上面两条指令,offset并不满足ldr编码规则。第一种是负数,第二种不是8的倍数。
但实际都可以编译通过,因为在编译过程中其被替换成
image.png
LDUR偏移量范围是-256~256
image.png
LDUR编码格式

register pair (offset)

image.png
可以看到,offset范围更小,0~127
image.png

实验:使用ldr / str指令实现memcpy函数

image.png
说明:b.cc执行条件码
① b.cc执行的条件码CC,也就是PSTATE寄存器中的C标志位没有被置位
② 对于SUBS指令(CMP指令通过SUBS指令实现),当没有发生算术运算借位(相当于x1-x2)时,C标志位为1;当发生算术运算借位时,C标志位为0
所以,对于条件码CC, 如果x1 < x2, c标志位为0,否则为1.
B通过检查标志位来决定是否跳转

STR(immediate)指令编码

STR(immediate)的指令编码方式与LDR(immediate)类似,各字段编码方式相同,此处不再赘述

Register 指令编码

LDR(register)指令编码

image.png
Xt:Rt,64位目的寄存器
Xn:Rn,基址寄存器
Rm:提供偏移量寄存器
:为扩展或移位标识符,编码在option字段,共有如下4种,
字段的默认值为LSL,但是当指令中省略字段时,LSL也会被忽略

: 移位量,可选,当<extend>不是LSL时默认为#0,
② 对于32位形式,取值为0(默认值)或2(S=1)
②对于64位形式,取值为0(默认值)或3(S=1)

举例:

image.pngimage.pngimage.pngimage.png

注意

根据手册,64位形式中,合法的移位值只有0和3。编译如下移位值不合法的指令,
image.png
编译会报出移位值无效错误

Literal 指令编码

LDR(literal)指令编码

image.png

  1. 编码到imm19中的内容,为标号到当前PC的偏移量
  2. 编码时以4B为单位,因此imm19可取值的范围为2^(19 + 2) = 2^21,体现在偏移量上就是±1MB
  3. 最终是从[PC + imm19编码的偏移量]处加载值到目标寄存器中
举例:

image.pngimage.png
Ldr编码格式
① 当执行到ldr指令时,将PC – 4(imm19)(越过mov指令),即可得到PC-relative的label地址
② 而且确实可以看出,PC-relative寻址是地址无关操作

LDR伪指令

image.png
LDR伪指令主要用于加载立即数,
① 当要加载的立即数可以由MOV等指令编码时,则被编译为MOV等指令
② 当要加载的立即数不能由MOV等指令编码时,则使用literal pool实现

举例:

image.png
此处被编译为LDR(literal)指令(将label的链接地址编码进imm19),加载到x0寄存器中的是label的链接地址。之所以使用ldr伪指令是因为不能使用MOV指令将label的地址存进x0.
还有一种即想存的常数不是立即数,因为不满足立即数编码格式,此时可以使用ldr伪指令将此常数编码进imm19中

猜你喜欢

转载自blog.csdn.net/qq_52353238/article/details/131752445