ARM 汇编笔记

GNU ARM 汇编程序格式

整体结构

.section .mysection, "x"
.text
.global test
test:
    ADD r0, r0, r1
    MOV pc, lr
.data   /*定义数据段,即程序运行需要用到的数据*/
stack:  .space 4*512    ; 分配 4*512 大小的数据空间,缺省值为 0
.end                    ; 汇编文件结束标志,常常省略不用
  • 在 GNU ARM 汇编语言程序中,以段 section 为单位组织代码。段是相对独立的指令或数据序列,可以具有特定的名称。
  • a允许段,w可写段,x执行段
  • 每个 section 段又可以包含代码段 .text 、数据段 .data 及未初始化的数据段 .bss 等子段。
  • 每一个段以段名为开始,以下一个段名或者文件结尾为结束。
  • 绝大多数汇编程序只有一个 section 段,可省略.section 的声明。

只有一个 section 段的简单汇编程序:

.text
.global _start
_start:
    mov r0,#9
    mov r1,#15
    add r1,r1,r0 ;r1=r0+r1
stop:
    b stop
.data
stack: .space 4*512 ; 分配 4*512 大小的数据空间,缺省值为 0

语句格式

[<label>:] [<instruction or directive or pseudo-instruction>] ;comment

标号 (label)

  • GNU 汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
  • 在 ARM 汇编中,标号代表它所在的地址,因此也可以当作变量或者函数来使用。
  • 标号只能由 a~zA~Z0~9._等(由点、字母、数字、下划线等组成,除局部标号外,不能以数字开头)字符组成。
  • 语句标号不能与寄存器名、指令助记符、伪指令(操作)助记符、变量名同名。
  • 标号可以单独一行,也可以与汇编语句在同一行。

GNU 汇编语言需要定义入口点。汇编程序的缺省入口是 _start 标号,用户也可以在连接脚本文件中用 ENTRY 标志指明其它入口点,例:

.section .data
    <initializeddatahere>
.section .bss
    <uninitializeddatahere>
.section .text
.globl _start
_start:
    <instructioncodegoeshere>

操作助记符域 (OPERATION)

  • 操作助记符域可以为指令、伪操作、宏指令或伪指令的助记符。
  • ARM 汇编器对大小写敏感,在汇编语言程序设计中,每一条指令的助记符可以全部用大写、或全部用小写,但不允许在一条指令中大、小写混用。
  • 指令助记符和后面的操作数或操作寄存器之间必须有空格,不可以在这之间使用逗号。

操作数域 (OPERAND)

操作数域表示操作的对象,操作数可以是常量、变量、标号、寄存器名或表达式,不同对象之间必须用逗号 , 分开,直接操作数前加 #
其中常量的表示方法如下:

  • 十进制数以非 0 数字开头,如:1239876
  • 二进制数以 0b 开头,其中字母也可以为大写
  • 八进制数以 0 开始,如:0456, 0123
  • 十六进制数以 0x 开头,如:0xabcd, 0X123f

注释域 (COMMENT)

ARM 汇编使用 ; 或者 /*...*/ 标注注释。

ARM 指令的基本格式

opcode {<cond>}{S} <Rd>,<Rn> {,<shifter operand2>}

其中 <> 中的项是必须的, {} 中的项是可选的。
opcode 表示指令助记符,比如 LDRSTRADD 等。

  • cond: 表示执行条件,如 EQNE 等。
  • S: 表示是否影响 CPSR 寄存器的值。
  • Rd: 表示目标寄存器
  • Rn: 表示第一个操作数的寄存器
  • Shifteroperand2: 表示第 2 个操作数

执行条件码

几乎所有的 ARM 指令均可包含一个可选的条件码,句法说明中以 {cond} 表示,只有在 CPSR 中的条件码标志满足指定的条件时,带条件码的指令才能执行。并使用后缀 S 来区分是否根据执行结果修改条件码标志。

操作码 [31:28] 助记符后缀 标志 含义
0000 EQ Z 置位 相等
0001 NE Z 清零 不等
0010 CS/HS C 置位 大于或等于(无符号>=)
0011 CC/LO C 清零 小于(无符号<)
0100 MI N 置位
0101 PL N 清零 正或零
0110 VS V 置位 溢出
0111 VC V 清零 未溢出
1000 HI C 置位且 Z 清零 大于(无符号>)
1001 LS C 清零或 Z 置位 小于或等于(无符号<=)
1010 GE N 和 V 相同 带符号>=
1011 LT N 和 V 不同 带符号<
1100 GT Z 清零且 N 和 V 相同 带符号>
1101 LE Z 置位或 N 和 V 不同 带符号<=
1110 AL 任何 总是(通常省略)

ARM 指令的寻址方式

寻址方式是根据指令中给出的地址码字段来寻找真实操作数地址的方式。ARM 处理器支持的基本寻址方式有:

  • 立即寻址
  • 寄存器寻址
  • 寄存器移位寻址
  • 寄存器间接寻址
  • 基址寻址
  • 多寄存器寻址
  • 堆栈寻址
  • 块拷贝寻址
  • 相对寻址

立即数寻址

操作数在指令中直接给出。

MOV R0,#0xFF
ADD R3,R3,#1
AND R8,R7,#0xff

并不是所有的立即数都可以作为第二操作数。有效立即数必须为一个 8 位常数循环右移偶数位间接得到。

寄存器寻址

所需要的值在寄存器中,指令中地址码给出的是寄存器编
号,即寄存器的内容为操作数。

ADD R0,R1,R2

这条指令将 2 个寄存器(R1 和 R2)的内容相加,结果放入第 3 个寄存器 R0 中。必须注意写操作数的顺序,第 1 个是结果寄存器,然后是第一操作数寄存器,最后是第二操作数寄存器。

寄存器移位寻址

该方式中,第 2 个寄存器操作数在与第 1 个操作数结合之前,先进行移位操作。

ADD R3,R2,R1,LSL #3

上述指令中,寄存器 R1 的内容先逻辑左移 3 位,再与寄存器 R2 内容相加,结果放入 R3 中。

移位不额外花费时间,且不影响 R1 的值。

LSL

逻辑左移 (Logical Shift Left)
寄存器中字的低端空出的位补 0。

LSL

LSR

逻辑右移 (Logical Shift Right)
寄存器中字的高端空出的位补 0。

LSR

ASR

算术右移 (Arithmetic Shift Right)
算术移位的对象是带符号数,若源操作数为正数,则字的高端空出的位补 0。若源操作数为负数,则字的高端空出的位补 1。

ASR

ROR

循环右移 (ROtate Right)
从字的最低端移出的位填入字的高端空出的位。

ROR

RRX

扩展为 1 的循环右移 (Rotate Right eXtended by 1 place)
操作数右移一位,空位(位 [31] )用原 C 标志值填充。

RRX

寄存器间接寻址

指令中的地址码给出某一通用寄存器的编号。在被指定的寄存器中存放操作数的有效地址,而操作数则存放在存储单元中,即寄存器为地址指针。
指令为 LDR / STR

基址寻址

变址寻址就是将基址寄存器的内容与指令中给出的位移量相加,形成操作数有效地址。变址寻址用于访问基址附近的存储单元,包括基址加偏移和基址加索引寻址。寄存器间接寻址是偏移量为 0 的基址加偏移寻址。

前索引寻址方式

基址需加(或减)最大 4KB 的偏移来计算访问的地址。

LDR R0,[R1,#4]       ;R0 <- [R1+4]

后索引寻址方式

基址不带偏移作为传送的地址,传送后自动索引。

LDR R0,[R1],#4      ;R0 <- [R1]
                    ;R1 <- R1+4

自动变址的前索引寻址

基址需加(或减)最大 4KB 的偏移来计算访问的地址,同时更新基址寄存器,并不消耗额外时间。

LDR R0,[R1,#4];R0 <- [R1+4]
                    ;R1 <- R1+4

基址加索引寻址

指令指定一个基址寄存器,再指定另一个寄存器(索引),其值作为位移加到基址上形成存储器地址。

LDR R0,[R1,R2]      ;R0 <- [R1+R2]

多寄存器寻址

该类指令支持批量数据加载/存储,可以一次在一片连续的存储器单元和多个寄存器之间传送数据。常用的加载/存储指令有 LDM (或 STM )指令,指令格式为:

LDM(STM)    {类型}  基址寄存器{!}, 寄存器列表{∧}

LDM (或 STM )指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。

类型

类型 含义
IA 每次传送后地址加 4
IB 每次传送前地址加 4
DA 每次传送后地址减 4
DB 每次传送前地址减 4
FD 满递减堆栈
ED 空递减堆栈
FA 满递增堆栈
EA 空递增堆栈

数据块传送

堆栈寻址

例子

LDMIA   R0, {R1,R3-R5}      ;R1 <- [R0], R3 <- [R0+4]
                            ;R4 <- [R0+8], R5 <- [R0+12]

由于传送的数据项总是 32 位的字,基址 R0 应该字对准。这条指令将 R0 指向的连续存储单元的内容送到寄存器 R1、R3、R4、R5 中。

LDMIA   R0!, {R1,R3-R5}     ;R1 <- [R0], R3 <- [R0+4]
                            ;R4 <- [R0+8], R5 <- [R0+12]

后缀 ! 表示最后的地址写回到 R0 中

常用 ARM 指令

# 为前缀,十六进制值以在 # 后加 0x& 表示。

LDR/STR

加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作。

LDR(STR){条件}      目的寄存器,<存储器地址>

条件后可跟加载模式:

  • 默认:字数据
  • T: 用户模式
  • S: 有符号数
  • B: 字节数据(Rd[7:0] 有效,Rd[31:8]=0)
  • H: 半字数据(Rd[15:0] 有效,Rd[31:16]=0)

存储器地址的形式:

指令格式 说明 操作
LDR <Rd>, [Rn] 零偏移 Rn 为传送地址,Rd <- [Rn]
LDR <Rd>, [Rn, offset][!] 前索引偏移 Rd←[Rn+offset] !=Rn <- Rn+offset
LDR <Rd>, label 程序相对偏移 偏移量=±4KB 范围
LDR <Rd>, [Rn], offset 后索引偏移 Rd <- [Rn] Rn <- Rn+offset

SWP

SWP{cond}{B}    Rd, Rm, [Rn]

在寄存器和存储器之间,由一次存储器读和一次存储器写组成的原子操作。完成一个字节或字的交换。

SWP R0,R1,[R2]       ;R0 <- [R2], [R2] <- R1
SWP R1,R1,[R2]       ;R1 <- [R2], [R2] <- R1

Rn!=Rm 或 Rd,Rm 可与 Rd 相同,此时 Rd <-> [Rn]
字节交换:[Rn] 单元的字节数据 -> Rd(高 24 位清零),Rm 中低 8 位 -> [Rn]

MOV/MVN

数据传送:

MOV[<cond>][s] <Rd>, <op2>

数据取非传送:

MVN[<cond>][s] <Rd>, <op2>

带有 [s] 的指令缺省不更新 CPSR 标志位,需更新时要加上 S

算数运算指令

指令格式 说明 操作
ADD[<cond>][s] Rd,Rn,op2 加法 Rd <- Rn+op2
ADC[<cond>][s] Rd,Rn,op2 带进位加法 Rd <- Rn+op2+C
SUB[<cond>][s] Rd,Rn,op2 减法 Rd <- Rn-op2
SBC[<cond>][s] Rd,Rn,op2 带进位减法 Rd <- Rn-op2-C
RSB[<cond>][s] Rd,Rn,op2 逆向减法 Rd <- op2-Rn
RSC[<cond>][s] Rd,Rn,op2 带进位逆向减法 Rd <- op2-Rn-C

s 时,影响 NVCZ 标志,除非 Rd=R15 (异常模式下,恢复标志)

对标志 C 的影响:
ADD / ADC : C=1 有进位, C=0 无进位
SUB / SBC / RSC : C=1 无借位, C=0 有借位

逻辑运算指令

指令格式 说明 操作
AND[<cond>][s] Rd,Rn,op2 逻辑与操作 Rd <- Rn& op2
ORR[<cond>][s] Rd,Rn,op2 逻辑或操作 Rd <- Rn | op2
EOR[<cond>][s] Rd,Rn,op2 逻辑异或操作 Rd <- Rn ^ op2
BIC[<cond>][s] Rd,Rn,op2 位清除 Rd <- Rn & (op2)

AND 运算通常用于某些位置 0

ORR 运算通常用于某些位置 1

EOR 运算通常用于某些位取反

比较运算指令

指令格式 说明 操作
CMP[<cond>] Rn, op2 两数相减,结果影响标志位 Rn-op2
CMN[<cond>] Rn, op2 两数相加,结果影响标志位 Rn-(-op2)
TST[<cond>] Rn, op2 位测试指令 标志 <- Rn & op2
TEQ[<cond>] Rn, op2 相等测试指令 标志 <- Rn ^ op2

分支指令

指令格式 说明 操作
B[<cond>] label 分支指令 PC <- label
BL[<cond>] label 带链接的分支指令 LR <- PC-4 , PC <- label
BX[<cond>] Rm 带状态切换的分支指令 PC <- Rm

协处理器指令

指令格式 说明 操作
MCR coproc,opcode1,Rd,CRn,CRm{,opcode2} ARM 寄存器到协处理器寄存器的数据传送指令 ARM -> 协处理器
MRC coproc,opcode1,Rd,CRn,CRm{,opcode2} 协处理器寄存器到 ARM 寄存器到的数据传送指令 协处理器 <- ARM
  • coproc: 协处理器名称(p0-p15)
  • opc10-7 之间的处理器特定编码。
  • Rd : 向协处理器传送值的 ARM 核寄存器(r0-r14)
  • CRn : 目的协处理器寄存器
  • CRm : 附加源或者目的协处理器寄存器
  • opc2 : 0-7 之间的处理器特定编码,缺省为 0

程序状态寄存器读写指令

指令格式 说明 操作
MRS[<cond>] Rd,psr 读 PSR Rd <- psr
MSR[<cond>] psr_fields,Rm 写 PSR psr_fields <- Rm
MSR[<cond>] psr_fields,#imm_8 写 PSR psr_fields <- #imm_8
  • Rd ≠ R15
  • psr = CPSR/SPSR
  • fields = f 条件标志位域 PSR[31:24]
  • fields = s 状态位域 PSR[23:16]
  • fields = x 扩展位域 PSR[15:8]
  • fields = c 控制位域 PSR[7:0]

PSR

特权模式下才能写 PSR,不能通过 MSR 指令直接修改 T 位实现 ARM/Thumb 切换,必须使用 BX 指令完成处理器状态的切换。

SWI

SWI[<cond>]    immed_24

产生软中断,处理器进入管理模式

ADR/ADRL

将程序相对偏移或寄存器相对偏移地址加载到寄存器中。

ADR[<cond>]    Rd, label
  • label 必须与 ADR 指令在同一个代码段中
  • 当地址是字对齐时,label 取值范围为 -1024~1024 字节
  • 当使用 ADRL 指令时,label 取值范围为 -256K~256K 字节

LDR 伪指令

用 32 位常量或一个地址加载寄存器

LDR[<cond>]    register, =[expr|label-expr]
  • register: 加载的寄存器
  • expr: 赋值成数字常量
  • label-expr: 程序相对偏移或外部表达式

例:

LDR     R3,=0xFF0   ; 把立即数 0xFF0 赋值给 R3
LDR     R3,=place   ; 把标号 place 对应的地址赋值给 R2

从指令到文字池的偏移量必须小于 4KB
与 ARM 指令的 LDR 相比,伪指令的 LDR 的参数有 =

NOP

空操作,可用于延时操作。

数据定义伪操作

数据定义伪操作一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。

指令 说明 示例
.byte 定义单字节 .byte0x12 ; 分配一个字节的空间并初始化为 0x12
.short 定义双字节数据 .short 0x1234
.long / .word 定义 4 字节数据 .word 0x12345678
.quad 定义 8 字节 .quad 0x1234567812345678
.float 定义浮点数 .float 0f3.2
.string / .asciz / .ascii 定义字符串 .ascii "abcd\0"
.space 分配任意的字节空间 .space 4*512,35 ; 分配 4*512 字节的内存空间并初始化为 35

符号定义伪操作

.equ

.equ 伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于 C 语言中的 #define

.equ        symbol, expr
  • symbol: 等效字符
  • expr: 常量或者标号

例:

.equ    x,2020      ; 将 x 定义为数值 2020
.equ    y,_start    ; y= 标号 _start 的值

使用等价伪指令 .equ 定义的符号名不会被系统分配存储空间。

.global

.glabal 伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。

.global    label

例:

.glabal    _start   ; 将标号 _start 定义为全局标号

例子

求 1~100 的和

START:
 MOV R0, #1
 MOV R1, #1
LOOP:
 ADD R1, R1, #1
 ADD R0, R0, R1
 CMP R1, #100
 BEQ END
 BNE LOOP
END:
 B END      ; R0 中就是和

5!

START:
 MOV R0, #5
 MOV R1, #4
LOOP:
 MUL R0, R0, R1
 SUB R1, R1, #1
 CMP R1, #0
 BEQ END
 BNE LOOP
END:
 B END      ; R0 中就是阶乘

转载规则

《ARM 汇编笔记》Konata 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
  目录