ARM指令集——ARM汇编语言

ARM汇编语言

基本特征

RISC,精简指令集。CISC的指令比较复杂,通常可以解码为对应的一系列内部操作序列(microcode),RISC相比之下指令更简单,有更高的能耗比。

无法直接对内存值进行操作,需要通过Load-Store操作进行嫁接。提供了条件按执行和块复制能力。

指令格式方面,第一个操作数一般表示目的地,除了LoadStore作为特例。

ARMv7时32位load/store架构处理器,字长32位。

Thumb

Thumb指令集在ARM7TDMI处理器中首次添加,只支持16位指令,用性能换来了更小的程序大小。

Thumb2扩展了Thumb指令来实现对16位和32位的混合,从而在性能和程序大小之间取得了平衡。

CPSR中T=0表示处于ARM指令集状态,T=1表示处于Thumb状态。

GNU汇编器

1
arm-none-eabi-as -g -o filename.o input.s

可以进行保留debug信息的汇编。

1
arm-none-eabi-ld -g -o filename.elf input.o

可以进行链接。

arm-none-eabi-gdb和arm-none-eabi-insight用来调试

识别源代码目标指令集和目标汇编器

  • UAL中汇编指令立即数的#号可以忽略
  • UAL中条件语句需要被IT指令立刻处理
  • UAL中.thumb, .arm用来标记代码是arm还是thumb
  • 16bitThumb汇编通常有两个操作数,只有branch可以条件执行。

GNU汇编器语法

每行汇编源码的格式是label: instruction @ comment

  • label: 表示对该行的地址进行标记,从而使改行可以作为跳转目标。
  • instruction可以是ARM汇编指令,也可以是对于汇编器的指令。
  • @后的内容均会被忽略,是注释。

当链接时,如果源代码中没有显式指定入口点,则可以在命令行中主动指定。

汇编器指令

所有指令以.开始

一个程序至少要包含一个.text代码段,读写的数据可以保存在.data段中,只读常量保存在.rodata段,零初始化常量可以保存在.bss中。

  • .align n 在data段中用0填充,在代码段中用NOP填充,从而让下一个位置和$2^n$字节对齐

  • .ascii "string"插入ascii字符串,多个字符串之间可以用逗号分隔

  • asciiz与ascii相同,以0结尾

  • .byte xxx,.hword xxx,.word xxx

  • .data让接下来的生命放置在可执行文件的data段中

  • .end标记源代码文件的结束,汇编器不对之后的内容进行解析

  • .equ symbol, expression用expression代替symbol

  • .extern symbol表明symbol在其他源代码文件中定义

  • .global symbol表明symbol是所有原代码文件中可见的

  • .include "filename"从filename中添加所有内容到当前文件。

  • .text将接下来的内容放置在输出文件中的text段。所有汇编指令位于其中。

表达式

  • 十进制
  • 0x前缀十六进制
  • 0b前缀二进制
  • 单引号包围的ASCII字符
  • 使用标准数学运算和逻辑运算对绝对常量或地址标记进行计算的结果

ARM汇编语言语法

寄存器

  • R0-R15
  • SP(R13)
  • FP(R11)
  • LR(R14)
  • PC(R15)
  • PSR寄存器

切换arm/thumb模式

改变CPSR中T位可以切换模式。

BX/BLX可以清空流水线以避免错误解码,对PC的LDR/POP/LDM也可以达成清空流水线的目的。BX/BLX跳转目标的第零位可以指定目标地址的指令集是ARM还是Thumb。

指令分类

  1. 数据处理操作
  2. 内存访问操作
  3. 控制流操作
  4. 系统操作

指令基础

ARM指令集中,12位立即数,即-2048到2047的立即数范围,ARM中被划分为8bit的常数和4bit的步长为2的向右移位,编译器需要负责正确转化立即数,汇编器会检查立即数错误。

例如,0x2300_0000可以表示为8位的0x23和4位的0x4,0x4表示向右位移4*2=8个位置。

  • MOV32:MOVW将16位常数移动到低16位,MOVT则是高十六位,构成该伪指令。
  • LDR Rn, =<constant>:通过使用代码段的字符池或者MOV、MVN来实现加载常量。手动指定.ltorg可以指定字符池的放置位置。
  • ADR Rn, =labelADRL Rn, =label可以让汇编器通过相对当前PC的加减操作向寄存器中存入label代表的地址。

ARM中几乎所有指令都可以是条件执行的,而非其他指令集中只有跳转可以条件执行,这样可以在较小的范围内避免分支跳转

如下是一个示例,向R2中存入R1、R0中较小的那个。虽然更短,但是对于较新的处理器这样做会更慢,因为分支预测可以做的更快。

1
2
3
CMP      R0, R1
MOVGE    R2, R1  @ R1 is less thanor equal to R0
MOVLT    R2, R0  @ R0 is less than R1

对于数据处理操作,PSR比较位会在以S为后缀的指令中被设置。

IT指令提供了最多四条指令的条件执行。

运算指令

常见指令

Operation{cond}{S} Rd, Rn, Operand2

cond表示条件执行

s表示是否写入CPSR

指令 参数 作用
ADD Rd,Rn,Op2
MOV Rd,Op2
RSB Rd,Rn,Op2 Rd = Op2-Rn,用于被减数是常数
SUB Rd,Rn,Op2
AND Rd,Rn,Op2
ORR Rd,Rn,Op2
CMP Rn,Op2
CMN Rn,Op2
TEQ Rn, Op2
BIC Rd,Rn,Op2 Rd=Rn&(~Op2)

第一个操作数必须是寄存器,第二个操作数可以是立即数/寄存器/寄存器的移位

移位实现

因此,ARM不需要任何单独的移位操作。寄存器的移位表示为Rm, shift #x,其中shift可以是LSL逻辑移位向左,LSR裸机移位向右,ASR算数移位向右,ROR循环移位向右,RRX循环移位扩展。

一个移位操作Rn=Rn»2可以表示为MOV Rn, Rn, LSR #2

乘法操作可以被优化为移位。除法亦可以进行优化。

乘法操作

乘法操作没有办法用立即数作为操作数,只能对两个寄存器操作。

指令 参数 作用
MLA Rd, Rn, Rm, Ra Rd=Ra+(Rn*Rm)
MLS Rd, Rn, Rm, Ra Rd=Ra-(Rn*Rm)
MUL Rd, Rn, Rm Rd = Rn*Rm
(S/U)(MLA/MUL)L RdLo, RdHi, Rn, Rm signed/unsigned RdHiLo saved result

SIMD操作

本质上是在32位寄存器中对16/8位等运算进行并行化处理。

对于SIMD操作,前缀通常有U、S、Q,分别代表无符号、有符号和饱和运算。其中饱和运算指的是如果大于最大则结果等于最大值。

绝对差相加

USADA8,对Rn、Rm的四个字节分别求绝对差并相加存到Rd

打包、解包

将高低十六位放到不同的寄存器。

UXTH Rd, RN将Rn低十六位存到Rd

PKHBT,Rd,Rn,Rm将Rn低16,Rm高16合并存放到Rd中。

饱和操作

内存操作

LDR、STR执行load、store操作。

可以用B指定字节,H指定半字,D指定双字。

S可以指定有符号,进行符号扩展,没有S则进行0扩展。

寻址方式

  1. 寄存器寻址
  2. 寄存器+偏移寻址:LDR Rd, [Rn, Rm], LDR Rd, [Rn, #n]
  3. 寄存器+偏移寻址+写回:LDR Rd, [Rn, Op2]!Load后将Rn+Op2写回Rn
  4. 寄存器+写回:LDR Rd, [Rn], #n,Load后及将Rn+n写回Rn

连续读取内存

设定一系列寄存器,保存连续读取到的内存值。通过这种方式可以减少地址加减操作的开销。

LDMIA R10!, {R0-R1, R3}将R10所指位置开始的三个字节依次存到R0、R1、R3中,并因为!可选地将R10递增3*4个字节。

LDM(I/D)(A/B)表示在当前地址之前(B)之后(A)递增(I)递减(D)

FD/FA/ED/EA后缀以堆栈角度设定方向。

跳转操作

B、BL与MIPS相同。想要切换指令集,使用BX、BLX。

不建议直接更改PC来实现跳转,但是使用LDR、LDM、POP等操作符从内存中读取到PC确实可以更改程序流。

Thumb下有CMP和B合并为的单指令跳转,分为CBZ和CBNZ表示零或非零时条件跳转。同时,有根据表中保存的相对PC位置跳转的指令TBB和TBH。

其他指令

协处理器指令

最多有16个协处理器可以被实现(CP0-CP15).

他们既可以存在于处理器之内,也可以连接到外部处理器。并非所有CortexA处理器支持这些操作。

CP15控制核心特性,包括缓存和MMU。CP14控制硬件debug功能。CP10、11提供对浮点和NEON硬件的访问。

如果指令执行,但是正确的协处理器不在系统中存在,则会发生指令未定义异常。

指令列表:

指令 作用
CDP 初始化协处理器数据处理操作
MRC move to register from CP
MCR 和MRC相反
LDC Load to CP
STC Store from CP
——
MRRC 从CP中存入一对寄存器
MCCR 从一对寄存器存入CP
LDCL 从多个寄存器读取一个CP寄存器?
STCL 向多个寄存器写入一个CP寄存器?

SVC(supervisor call)

最初被称为SWI(Software Interrupt),产生一个异常,由操作系统处理。

PSR操作指令

指令 作用
MRS move to register from PSR
MSR 将可以更新的位设置为给定寄存器中的值
CPS change processor state
SETEND 修改一个cpsr中的Ebit,切换大小端地址

bit操作指令

指令 作用
BFI bit field insert,将一个寄存器的几个连续bit插入到另一个寄存器中
BFC clear,清除寄存器中的连续bit
SBFX/UBFX 将相邻的位从一个寄存器拷贝到另一个并符号/无符号扩展到32位
RBIT 将寄存器所有位调换顺序

cache预加载

指令 作用
PLD PreLoad Data
PLI PreLoad Instration

反转字节序

指令 作用
REV 字内字节序反转
REV16 两个半字
REVSH 低半字并符号扩展

其他指令

指令 作用
BKPT 使核心进入debug模式。
WFI 释放核心,等待中断唤醒。
NOP do nothing
WFE 释放核心,等待事件。
SEV 产生唤醒事件,唤醒同一簇中其他核心。
Licensed under CC BY-NC-SA 4.0
京ICP备2021032224号-1
Built with Hugo
主题 StackJimmy 设计
vi ./themes/hugo-theme-learn/layouts/partials/footer.html