1. 引言
ARM 汇编语言是嵌入式开发、底层系统编程和逆向工程中不可或缺的基础技能。本文面向初学者,从 ARM 处理器的基本架构出发,逐步讲解常用指令、寄存器、寻址方式以及一个完整的汇编程序示例,帮助读者快速建立 ARM 汇编的整体认知。
2. ARM 架构基础
ARM 是一种精简指令集计算机(RISC)架构,其设计理念是让指令集保持简洁、规整,从而在低功耗的同时获得较高的执行效率。ARM 处理器广泛应用于移动设备、嵌入式系统、物联网终端等领域。
ARM 架构具有以下显著特点:
- 固定指令长度:ARM 模式下每条指令固定为 32 位,便于流水线取指和译码。
- 大量通用寄存器:提供 16 个 32 位通用寄存器(R0-R15),其中部分寄存器有特殊用途。
- 加载/存储架构:只有加载(LDR)和存储(STR)指令可以访问内存,其余指令均在寄存器之间完成运算。
- 条件执行:大多数指令可以附带条件码,根据标志位决定是否执行,减少分支跳转。
3. 寄存器与工作模式
ARM 处理器拥有 16 个 32 位通用寄存器,分别命名为 R0 到 R15。其中部分寄存器承担特殊功能:
| 寄存器 | 别名 | 用途说明 |
|---|---|---|
| R0-R12 | 通用寄存器 | 用于存放数据、地址和中间运算结果 |
| R13 | SP | 栈指针寄存器,指向当前栈顶 |
| R14 | LR | 链接寄存器,保存子程序返回地址 |
| R15 | PC | 程序计数器,指向当前正在执行的指令地址 |
此外,ARM 处理器还包含一个当前程序状态寄存器(CPSR),用于保存条件标志位(N、Z、C、V)、中断禁止位以及当前处理器模式等信息。
4. 常用指令详解
ARM 汇编指令数量较多,但初学者只需掌握以下几类核心指令即可编写大部分程序。
4.1 数据传送指令
armasm
MOV R0, #5 @ 将立即数 5 送入 R0
MOV R1, R0 @ 将 R0 的值复制到 R1
LDR R2, =0x1234 @ 将常量 0x1234 加载到 R2
STR R0, [R3] @ 将 R0 的值存储到 R3 指向的内存地址
4.2 算术运算指令
armasm
ADD R0, R1, R2 @ R0 = R1 + R2
SUB R0, R1, #1 @ R0 = R1 - 1
MUL R0, R1, R2 @ R0 = R1 * R2
ADC R0, R1, R2 @ R0 = R1 + R2 + 进位标志
4.3 逻辑运算指令
armasm
AND R0, R1, R2 @ R0 = R1 与 R2
ORR R0, R1, R2 @ R0 = R1 或 R2
EOR R0, R1, R2 @ R0 = R1 异或 R2
MVN R0, R1 @ R0 = R1 按位取反
4.4 分支跳转指令
armasm
B label @ 无条件跳转到 label
BEQ label @ 相等时跳转(Z 标志为 1)
BNE label @ 不相等时跳转(Z 标志为 0)
BL func @ 跳转到 func 并保存返回地址到 LR
5. 寻址方式
ARM 汇编提供了多种寻址方式,用于灵活地访问内存和寄存器中的数据。
- 立即数寻址 :操作数直接写在指令中,如
MOV R0, #100。 - 寄存器寻址 :操作数存放在寄存器中,如
MOV R1, R0。 - 寄存器间接寻址 :寄存器存放内存地址,如
LDR R0, [R1]。 - 基址变址寻址 :基址寄存器加偏移量,如
LDR R0, [R1, #4]。 - 前/后变址寻址 :先更新地址再访问,或先访问再更新地址,如
LDR R0, [R1, #4]!。
6. 完整汇编程序示例
下面是一个完整的 ARM 汇编程序,演示如何计算 1 到 10 的累加和,并将结果存入指定寄存器。
armasm
AREA Sum, CODE, READONLY
ENTRY
Start
MOV R0, #1 @ 循环变量 i,从 1 开始
MOV R1, #10 @ 循环上限 n = 10
MOV R2, #0 @ 累加和 sum = 0
Loop
ADD R2, R2, R0 @ sum = sum + i
ADD R0, R0, #1 @ i = i + 1
CMP R0, R1 @ 比较 i 与 n
BLE Loop @ 若 i <= n,继续循环
MOV R0, R2 @ 将结果放入 R0
END
该程序的核心逻辑是:使用 R0 作为循环计数器,R1 保存循环上限,R2 保存累加结果。每次循环将当前计数值累加到 R2,然后递增计数器并与上限比较,直到计数器超过上限为止。
7. 条件执行与标志位
ARM 汇编的一大特色是条件执行。大多数数据处理指令可以附带条件码,只有满足条件时才真正执行。条件码由 CPSR 中的标志位决定。
| 条件码 | 含义 | 标志位条件 |
|---|---|---|
| EQ | 相等 | Z = 1 |
| NE | 不相等 | Z = 0 |
| GT | 有符号大于 | Z = 0 且 N = V |
| LT | 有符号小于 | N 不等于 V |
| GE | 有符号大于等于 | N = V |
| LE | 有符号小于等于 | Z = 1 或 N 不等于 V |
条件执行可以减少分支指令的使用,提高代码密度和执行效率。例如,下面的代码使用条件执行实现两个数取较大值的功能:
armasm
CMP R0, R1 @ 比较 R0 与 R1
MOVGT R2, R0 @ 若 R0 > R1,则 R2 = R0
MOVLE R2, R1 @ 若 R0 <= R1,则 R2 = R1
8. 栈操作与子程序调用
在 ARM 汇编中,子程序调用通过 BL 指令实现,返回地址自动保存到 LR 寄存器。子程序返回时使用 MOV PC, LR 或 BX LR 指令。
armasm
AREA Func, CODE, READONLY
ENTRY
Start
MOV R0, #5
MOV R1, #3
BL AddFunc @ 调用子程序
MOV R2, R0 @ 结果存入 R2
B Stop
AddFunc
ADD R0, R0, R1 @ R0 = R0 + R1
MOV PC, LR @ 返回调用点
END
当子程序内部需要嵌套调用其他子程序时,必须先将 LR 压栈保存,否则返回地址会被覆盖。栈操作使用 PUSH 和 POP 指令:
armasm
PUSH {LR} @ 保存返回地址
BL OtherFunc @ 嵌套调用
POP {PC} @ 恢复返回地址并返回
9. 总结
本文介绍了 ARM 汇编的基础知识,包括架构特点、寄存器结构、常用指令、寻址方式、条件执行以及子程序调用等内容。掌握这些基础后,读者可以进一步学习 ARM 的异常处理、中断机制、Thumb 指令集以及汇编与 C 语言的混合编程等进阶主题。
建议初学者在真实开发板或模拟器(如 QEMU、Keil MDK)上动手实践,通过编写和调试实际程序来加深对 ARM 汇编的理解。