9.8 KiB
tags, create time
| tags | create time | |||
|---|---|---|---|---|
|
2026-06-10 11:11 |
ARM指令集与汇编
概述
本文档详细讲解ARM指令集体系,包括ARM/Thumb指令集对比、指令格式、核心指令详解、条件码、寻址方式以及汇编程序设计。这是嵌入式系统课程中需要动手实践的重点部分。
正文
1. ARM指令集 vs Thumb指令集
| 比较维度 | ARM指令集 | Thumb指令集 |
|---|---|---|
| 指令宽度 | 32位 | 16位 |
| 功能 | 完整功能 | ARM的子集 |
| 代码密度 | 低(占用空间大) | 高(占用空间小) |
| 性能 | 高 | 略低 |
| 状态标志 | CPSR.T = 0 | CPSR.T = 1 |
[!warning] Thumb状态不能执行ARM指令 在Thumb状态下,处理器只能执行16位的Thumb指令。如果需要执行32位ARM指令,必须先切换到ARM状态(通过BX指令)。两种状态不能在同一时刻混合执行。
2. 指令格式
ARM指令的基本格式为:
opcode{condition}{S} Rd, operand1, operand2
| 字段 | 说明 |
|---|---|
| opcode | 操作码,如MOV, ADD, LDR |
| {condition} | 条件码,如EQ, NE, GT(可选) |
| {S} | 是否影响CPSR标志位(可选) |
| Rd | 目标寄存器 |
| operand1 | 第一操作数(通常是寄存器) |
| operand2 | 第二操作数(灵活,可带移位) |
graph LR
subgraph format["Instruction Format"]
direction LR
OP["opcode"] --> CON["condition"] --> S["S flag"] --> RD["Rd"] --> OP1["operand1"] --> OP2["operand2"]
end
style OP fill:#4fc3f7
style CON fill:#81d4fa
style S fill:#b3e5fc
style RD fill:#ffcc80
style OP1 fill:#ffab91
style OP2 fill:#a5d6a7
3. 桶形移位器(Barrel Shifter)
ARM指令的一个强大特性:第二操作数可以在送入ALU之前先进行移位操作。这意味着一条指令就能完成"移位+运算",无需额外的移位指令。
移位类型:
| 移位操作 | 全称 | 说明 |
|---|---|---|
| LSL | Logical Shift Left | 逻辑左移,低位补0 |
| LSR | Logical Shift Right | 逻辑右移,高位补0 |
| ASR | Arithmetic Shift Right | 算术右移,高位补符号位 |
| ROR | Rotate Right | 循环右移 |
; 示例:利用桶形移位器
ADD R0, R1, R1, LSL #2 ; R0 = R1 + R1 * 4 = R1 * 5
SUB R2, R3, R3, LSR #1 ; R2 = R3 - R3/2 = R3/2
MOV R4, R5, ROR #8 ; R4 = R5循环右移8位
[!tip] 桶形移位器的价值 在没有桶形移位器的架构中,
R1 * 5需要两条指令:先LSL #2再ADD。ARM用一条指令就完成了,这体现了RISC架构中"简单指令的巧妙组合"的设计哲学。
4. 核心指令详解
4.1 数据传送指令 MOV
MOV R0, #10 ; R0 = 10(立即数传送)
MOV R1, R0 ; R1 = R0(寄存器传送)
MOV R2, R0, LSL #3 ; R2 = R0 << 3(带移位传送)
MOV只能在寄存器之间传送数据,不能访问内存! 这是与LDR的关键区别。
4.2 加载/存储指令 LDR/STR
LDR R0, [R1] ; 从R1指向的内存地址加载数据到R0
STR R0, [R1] ; 将R0的值存储到R1指向的内存地址
LDR R0, [R1, #4] ; 从R1+4地址加载(基址+偏移)
STR R0, [R1], #4 ; 先存储,再更新R1 = R1 + 4(后索引)
LDR R0, [R1, #4]! ; 先更新R1 = R1 + 4,再加载(前索引)
[!question] MOV R0, #0xFF 和 LDR R0, =0x12345678 有什么区别?
MOV R0, #0xFF是真正ARM指令,0xFF是合法的8位立即数(通过循环右移编码)。LDR R0, =0x12345678是伪指令,汇编器会将0x12345678放入文字池(literal pool),然后用一条LDR从该地址加载。因为0x12345678无法编码为8位立即数。
4.3 算术运算指令
ADD R0, R1, R2 ; R0 = R1 + R2
ADD R0, R1, #5 ; R0 = R1 + 5
ADDS R0, R1, R2 ; R0 = R1 + R2,并更新CPSR标志位
SUB R0, R1, R2 ; R0 = R1 - R2
SUBS R0, R1, #1 ; R0 = R1 - 1,更新标志位
MUL R0, R1, R2 ; R0 = R1 * R2
注意带S后缀的指令会更新CPSR的N、Z、C、V标志位。
4.4 比较指令 CMP
CMP R0, R1 ; 比较R0和R1(计算R0-R1,但不保存结果)
BEQ label ; 如果相等(Z=1),跳转到label
BNE label ; 如果不相等(Z=0),跳转到label
CMP本质上是一条不保存结果的减法指令,只设置标志位。
4.5 分支指令 B / BL
B label ; 无条件跳转到label
BL function ; 跳转到function,同时将返回地址保存到LR
- B:简单跳转,用于循环、条件分支
- BL:带链接的跳转,用于函数调用(LR自动保存返回地址)
4.6 软中断指令 SWI
SWI #0x123456 ; 触发软中断,进入SVC模式
SWI用于从用户模式请求操作系统服务(系统调用)。执行SWI后:
- 进入SVC模式
- CPSR保存到SPSR_svc
- 返回地址保存到LR_svc
- PC跳转到向量地址0x00000008
4.7 批量加载/存储 STMFD / LDMFD
STMFD SP!, {R0-R3, LR} ; 将R0-R3和LR入栈(满递减栈)
LDMFD SP!, {R0-R3, PC} ; 从栈中恢复R0-R3,并将PC出栈实现返回
这两个指令常用于函数入口保存现场和函数出口恢复现场。FD表示Full Descending(满递减栈),是ARM的默认栈类型。
4.8 状态寄存器读写 MRS / MSR
MRS R0, CPSR ; 将CPSR读入R0
MSR CPSR_c, R0 ; 将R0写入CPSR的控制位域
MSR CPSR_f, R0 ; 将R0写入CPSR的标志位域
MRS/MSR用于在特权模式下修改CPSR,例如开关中断:
; 关中断
MRS R0, CPSR
ORR R0, R0, #0x80 ; I位(bit7)置1
MSR CPSR_c, R0
; 开中断
MRS R0, R1
BIC R0, R0, #0x80 ; I位(bit7)清0
MSR CPSR_c, R0
4.9 测试等价指令 TEQ
TEQ R0, R1 ; 按位异或(EOR),只设置标志位,不保存结果
TEQ用于测试两个值是否相等(或测试某些位),与CMP类似但使用异或运算。
5. 条件码
ARM支持15种条件码,基于CPSR的标志位进行判断:
| 条件码 | 含义 | 标志位条件 |
|---|---|---|
| EQ | 相等 | Z=1 |
| NE | 不相等 | Z=0 |
| GT | 大于(有符号) | Z=0且N=V |
| LT | 小于(有符号) | N!=V |
| GE | 大于等于(有符号) | N=V |
| LE | 小于等于(有符号) | Z=1或N!=V |
| HI | 无符号大于 | C=1且Z=0 |
| LS | 无符号小于等于 | C=0或Z=1 |
| CS/HS | 无符号大于等于 | C=1 |
| CC/LO | 无符号小于 | C=0 |
| PL | 正数或零 | N=0 |
| MI | 负数 | N=1 |
| VS | 溢出 | V=1 |
| VC | 无溢出 | V=0 |
| AL | 无条件(默认) | 任意 |
[!question] CMP R0, #0 后接 BMI label,什么时候会跳转? BMI是"负数"条件跳转。CMP R0, #0 实际上是R0 - 0,结果就是R0本身。如果R0 < 0(最高位为1),N标志位被置位,BMI条件成立,跳转到label。
6. 寻址方式
| 寻址方式 | 示例 | 说明 |
|---|---|---|
| 立即数寻址 | MOV R0, #10 |
操作数就在指令中 |
| 寄存器寻址 | ADD R0, R1, R2 |
操作数在寄存器中 |
| 寄存器间接寻址 | LDR R0, [R1] |
R1中存放的是内存地址 |
| 基址+偏移寻址 | LDR R0, [R1, #4] |
R1为基地址,4为偏移量 |
| 基址+索引寻址 | LDR R0, [R1, R2] |
R2的值作为索引偏移 |
7. 立即数编码
ARM指令是32位固定的,立即数编码只占用12位(4位旋转量 + 8位立即数值)。规则:
实际值 = 8位立即数 循环右移 (旋转量 * 2) 位
这意味着只有特定的值才能作为立即数。例如:
#0xFF:合法,8位全1,旋转0位#0x100:合法,#1循环右移24位#0x12345678:不合法,无法用8位循环右移表示
[!tip] 立即数合法性判断 判断一个数是否是合法的ARM立即数:将它写成二进制,看是否能通过循环右移偶数位变成8位以内的数。
8. 字对齐与大小端
数据宽度:
| 类型 | 位数 | 字节数 |
|---|---|---|
| Word(字) | 32位 | 4字节 |
| Halfword(半字) | 16位 | 2字节 |
| Byte(字节) | 8位 | 1字节 |
对齐要求:Word访问的地址必须是4的倍数(地址 mod 4 = 0),Halfword访问地址必须是2的倍数。
大小端:
- Little-Endian(小端):低字节存低地址(ARM常用)
- Big-Endian(大端):高字节存低地址
; 假设内存地址0x1000存放值 0x12345678
; 小端模式:
; 0x1000: 0x78 (最低字节)
; 0x1001: 0x56
; 0x1002: 0x34
; 0x1003: 0x12 (最高字节)
; 大端模式:
; 0x1000: 0x12 (最高字节)
; 0x1001: 0x34
; 0x1002: 0x56
; 0x1003: 0x78 (最低字节)
9. 完整示例:C if-else 对应的ARM汇编
以下是C语言if-else语句及其对应的ARM汇编实现:
// C代码
int max(int a, int b) {
if (a > b)
return a;
else
return b;
}
; ARM汇编实现
max:
CMP R0, R1 ; 比较a和b(R0-R1,设置标志位)
MOVGT R0, R0 ; 如果a > b(GT条件成立),R0保持不变
MOVLE R0, R1 ; 如果a <= b(LE条件成立),R0 = b
BX LR ; 返回,LR中保存了调用者的返回地址
更高效的写法:
; 优化版本:只用两条核心指令
max:
CMP R0, R1 ; 比较a和b
MOVLT R0, R1 ; 如果a < b,则R0 = b
BX LR ; 返回
[!question] 为什么优化版本只判断了LT一种情况? 因为如果a >= b,R0的值本来就是a,不需要任何操作。只有当a < b时才需要将b写入R0。这体现了"条件执行"的强大——ARM的每条指令都可以条件执行,避免了不必要的跳转。