ARM 汇编初体验

概述

大部分 arm core 提供两种指令集:

  • ARM 指令集(32-bit)
  • Thumb 指令集(16-bit)

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
    2
    ADD 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
2
3
4
if (a > b)
a++;
else
b++;

对应的汇编代码如下。其中R0为a,R1为b:

1
2
3
CMP   R0,R1      ; R0 与 R1 比较
ADDHI R0,R0,#1 ; if(a>b) a++;
ADDLS R1,R1,#1 ; if(a<=b) b++;

例2:

1
2
if (a != 1 && b != 2)
a = a+b;

对应的汇编代码如下。其中R0为a,R1为b:

1
2
3
CMP R0,#1      ; 比较 R0 与 1
CMPNE R1,#2 ; 若(R0 != 1), 则比较 R1 与 2
ADDNE R0,R0,R1 ; 若(R0 != 1 && R1 != 2), 则执行 R0=R0+R1

注意:

对此处汇编代码多作一些说明。上面第三行ADDNE R0,R0,R1根据条件来执行,而第二行的
比较指令执行与否依赖于第一行的比较指令的结果(对标志位产生的影响),若第一个比较
指令把 Z 标志位置 1, 则第二个比较指令不执行,因此也不会影响标志位,ADDNE就根据
CMP R0,#1的对标志位的影响来确定执行与否(也不执行)。对比如下汇编代码:

1
2
3
CMP R0,#1
CMPNE R1,#2
ADDEQ R0,R0,R1

如果第一个比较指令结果置 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
2
adds r0,r0,r2
adc r1,r1,r3

其中 adds 最后的 s 表示该条指令影响标志位。相应的一个 96 位减法的例子如下:

1
2
3
subs r3,r6,r9
sbcs r4,r7,r10
sbc r5,r8,r11

应该注意的是 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 指令同步隔离
0%