概述
大部分 arm core 提供两种指令集:
32 位架构中 arm 的一些编程约定:
- Byte (字节): 8 bits
- Halfword(半字): 16 bits
- Word (字): 32 bits
指令数大约 30 多条。有指令编码表可以进行手工汇编。
ARM 指令的一些特点:
- ARM 指令固定长度。
- ARM 指令是多功能的(怎么多功能?)。
- ARM 指令都带有条件。
- ARM 指令第二操作数很灵活。
ARM 寄存器
ARM 的寄存器有 30 多个,ARM 处理器区分不同的模式,不同的模式下,名字相同的寄存器,
实际并不是同一个。
User 模式下,寄存器有 r0~r15, cpsr 共17个寄存器。其中r13为堆栈指针寄存器(sp),
r14 为子程序链接寄存器(lr),r15 为程序计数器(pc)。
ARM 指令的基本格式
格式如下:
<opcode>{<cond>}{s} <Rd>, <Rn> {,<operand2>}
opcode: 指令助记符
cond: 执行条件
S: 是否影响 CPSR 突破口的值
Rd: 目标寄存器
Rn: 第 1 个操作数的寄存器
operand2: 第 2 个操作数
其中<>内的项是必须的,{}内的项是可选的。
使用条件码“cond”可以实现高效的逻辑操作,所有的ARM指令都可以条件执行,而Thumb指令
只有B(跳转)指令具有条件执行功能。无条件码时默认是无条件(AL)执行。其中的S项表示
该条指令是否影响CPSR寄存器的值,CMP指令是默认影响的,其他的包括 ADD 和 SUB 等指
令也可以影响 CPSR 寄存器的值,但要加上这个标记。
灵活使用第 2 个操作数能够提高代码效率。operand2 有如下形式:
#immed_8r – 常数表达式(立即数)
ARM 中不是所有的数都可以作为立即数,立即数共有 12 位组成,其中[7:0]位表示基数,
[11:8]位表示移位数,具体还是参考相关资料,此处不详述。Rm – 寄存器方式(操作数即为寄存器的数值)
Rm,shift – 寄存器移位方式
将寄存器的移位结果作为操作数,但 Rm 的值保持不变。
例如,1
2ADD R1,R1,R1,LSL #3 ; R1 = R1+R1*8 = 9*R1
SUB R1,R1,R2,LSR R3 ; R1 = R1-(R2/2^R3)
除标号外,其他指令前面必须有空白符,不能直接写在行首。
关于 ARM 汇编的立即数有限制的原因,我的理解是,ARM 是 RISC 体系结构的,指令是定
长的(32 bits),32 位 ARM 汇编中 int 型应该是32 bits,而指令的操作码等占用了指令
的一部分,所以不可能有 32 个 bits 来表示立即数,所以出现了一些补偿的办法,或者说
限制。ARM 甚至有一个 ldr 伪指令来处理这种情况(ldr 是一条汇编指令,同时也有伪指令
的形式)。
指令条件码
有4个bits表示条件码,可以有 $2^4=16$ 个组合,但是全1的组合表示“从不执行”,一般不
使用,所以有时也说有15条件码。
例子,条件码:
1 | if (a > b) |
对应的汇编代码如下。其中R0为a,R1为b:
1 | CMP R0,R1 ; R0 与 R1 比较 |
例2:
1 | if (a != 1 && b != 2) |
对应的汇编代码如下。其中R0为a,R1为b:
1 | CMP R0,#1 ; 比较 R0 与 1 |
注意:
对此处汇编代码多作一些说明。上面第三行ADDNE R0,R0,R1
根据条件来执行,而第二行的
比较指令执行与否依赖于第一行的比较指令的结果(对标志位产生的影响),若第一个比较
指令把 Z 标志位置 1, 则第二个比较指令不执行,因此也不会影响标志位,ADDNE
就根据CMP R0,#1
的对标志位的影响来确定执行与否(也不执行)。对比如下汇编代码:
1 | CMP R0,#1 |
如果第一个比较指令结果置 Z 为 1 则第二个比较指令不执行,第三行会执行。 我现在是
这样理解的,如果有误请指正。
ARM 指令一览
数据处理指令
数据处理指令只能处理寄存器中的数据,内存中的数据需要通过加载指令加载到寄存器中进行处理。
助记符 | 说明 | 操作 |
---|---|---|
MOV Rd,operand2 |
数据传送 | Rd <- operand2 |
MVN Rd,operand2 |
数据非传送 | Rd <- (~operand2) |
算术运算指令
助记符 | 说明 | 操作 |
---|---|---|
ADD Rd,Rn,operand2 |
加法 | Rd <- Rn + operand2 |
SUB Rd,Rn,operand2 |
减法 | Rd <- Rn - operand2 |
RSB Rd,Rn,operand2 |
逆向减法 | Rd <- operand2 - Rn |
ADC Rd,Rn,operand2 |
带进位加法 | Rd <- Rn + operand2 + Carry |
SBC Rd,Rn,operand2 |
带进位减法 | Rd <- Rn - operand2 - (NOT)Carry |
RSC Rd,Rn,operand2 |
带进位逆向减法 | Rd <- operand2 - Rn - (NOT) Carry |
例子:
这个小例子实现 64 位的加法,两个数分别存放在 [r1,r0] 和 [r3,r2] 中,结果存放在
[r1,r0]中。
1 | adds r0,r0,r2 |
其中 adds 最后的 s 表示该条指令影响标志位。相应的一个 96 位减法的例子如下:
1 | subs r3,r6,r9 |
应该注意的是 sbc 指令最后是减去 Carry 位的反码,这是因为减法运算(包括 CMP),当
运算产生了借位时(无符号数溢出),C=0,否则 C=1。
逻辑运算指令
助记符 | 说明 | 操作 |
---|---|---|
AND Rd,Rn,operand2 |
逻辑与 | Rd <- Rn & operand2 |
ORR Rd,Rn,operand2 |
逻辑或 | Rd <- Rn | operand2 |
EOR Rd,Rn,operand2 |
逻辑异或 | Rd <- Rn ^ operand2 |
BIC Rd,Rn,operand2 |
位清除 | Rd <- Rn & (~operand2) |
比较指令
比较指令不保存运算结果,只影响相应的标志位。
助记符 | 说明 | 操作 |
---|---|---|
CMP Rn,operand2 |
比较 | 标志位(NZCV) <- Rn - operand2 |
CMN Rn,operand2 |
负数比较 | 标志位(NZCV <- Rn + operand2 |
TST Rn,operand2 |
位测试 | 标志位(NZCV <- Rn & operand2 |
TEQ Rn,operand2 |
相等测试指令 | 标志位(NZCV <- Rn ^ operand2 |
TST
指令测试相应比特位是否置位,主要是影响 Z 位,通常与 EQ 和 NE 条件码配合使用,当所有测试位均
为 0 时,EQ 有效,否则 NE 有效。
CPSR/SPSR 读写指令
助记符 | 说明 |
---|---|
MSR{cond} APSR_flags, Rm |
对 CPSR/SPSR 进行写操作 |
MRS{cond} Rn, CPSR/SPSR |
对 CPSR/SPSR 进行读操作 |
协处理器指令
助记符 | 说明 |
---|---|
MCR |
ARM寄存器到协处理器的寄存器的数据传送 |
MRC |
协处理器的寄存器到ARM寄存器的数据传送 |
CDP |
协处理器数据操作指令 |
LDC |
协处理器数据加载指令 |
STC |
协处理器数据存储指令 |
隔离(同步)指令
助记符 | 说明 |
---|---|
DMB | 数据存储器隔离 |
DSB | 数据同步隔离 |
ISB | 指令同步隔离 |