1. ARM汇编指令基础解析
作为一名嵌入式开发工程师,我经常需要与ARM汇编打交道。今天我想分享一些ARM汇编指令的核心知识点,这些都是我在实际项目中积累的经验总结。
1.1 ARM指令基本格式
ARM汇编指令的标准格式为:指令{S}{条件码} 目标寄存器, 操作数1, 操作数2
其中:
{S}后缀表示运算完成后更新CPSR的条件标志位(N/Z/C/V){条件码}可以让指令有条件执行,这在分支控制中非常有用
举个例子:
arm复制MOV R0, #5 ; 简单赋值
ADDS R0, R0, R1 ; 带状态更新的加法
MOVEQ R0, #0 ; 仅在Z=1时执行
1.2 数据处理指令详解
ARM的数据处理指令非常丰富,这里我整理了一个常用指令速查表:
| 指令 | 功能 | 示例 | 注意事项 |
|---|---|---|---|
| MOV | 数据传送 | MOV R0, #5 |
立即数有限制 |
| MVN | 取反传送 | MVN R0, #0 |
结果=0xFFFFFFFF |
| ADD | 加法 | ADD R0, R1, R2 |
不更新状态位 |
| ADDS | 带状态加法 | ADDS R0, R0, R1 |
会更新CPSR |
| SUB | 减法 | SUB R0, R1, #1 |
常用作计数器递减 |
| AND | 逻辑与 | AND R0, R0, #0xF0 |
用于位掩码 |
| ORR | 逻辑或 | ORR R0, R0, #0x01 |
用于位置1 |
| EOR | 异或 | EOR R0, R1, #0xFF |
用于位翻转 |
| BIC | 位清除 | BIC R0, R0, #0x1F |
用于位清零 |
在实际开发中,我发现EOR和BIC这两个指令特别有用:
- EOR可以实现位翻转和相等判断
- BIC常用于清除特定位,比如清除CPSR的模式位
2. ARM立即数与伪指令
2.1 立即数限制
ARM指令固定32位,留给立即数的空间只有12位(8位数值+4位移位量)。这意味着不是所有32位数都能直接用作立即数。
合法立即数的规则:
- 8位常数经过偶数次循环右移能得到的值
- 例如0xFF、0x100(0x01<<8)、0xFF00都是合法的
- 但0x40001000、0x123456等无法通过8位循环移位得到的数就是非法的
arm复制MOV R0, #0xFF ; 合法
MOV R0, #0x100 ; 合法(1左移8位)
MOV R0, #0x40001000 ; 非法!汇编器会报错
2.2 LDR伪指令
当我们需要加载非法立即数时,可以使用LDR伪指令:
arm复制LDR SP, =0x40001000 ; 伪指令,加载任意32位地址到SP
汇编器会智能处理:
- 如果值能用MOV表示,直接转换为MOV指令
- 否则,在代码段附近生成"文字池",将常数存入后用内存读取指令加载
提示:文字池是汇编器自动生成的数据区域,用于存储大立即数。理解这个机制对调试很有帮助。
3. 64位运算与条件执行
3.1 64位加法实现
ARM是32位处理器,要实现64位加法需要两条指令配合:
arm复制; 计算0x1FFFFFFFF + 0x4
MOV R0, #0xFFFFFFFF ; 低32位
MOV R1, #0x1 ; 高32位
MOV R2, #0x4 ; 加数
MOV R3, #0x0 ; 高32位加数
ADDS R0, R0, R2 ; 低32位相加,更新CPSR
ADC R1, R1, R3 ; 高32位相加+进位
; 结果:R1:R0 = 0x2:0x00000003
关键点:
- ADDS更新进位标志C
- ADC使用进位标志进行高位计算
- 这种技巧可以扩展到任意位宽的运算
3.2 条件码与分支控制
ARM的条件执行是其一大特色,几乎所有指令都可以条件执行:
| 条件码 | 含义 | 判断条件 | 典型应用 |
|---|---|---|---|
| EQ | 相等 | Z=1 | if(a==b) |
| NE | 不等 | Z=0 | if(a!=b) |
| GT | 大于 | Z=0且N=V | if(a>b) |
| LT | 小于 | N!=V | if(a<b) |
| CS | 进位 | C=1 | 无符号比较 |
| AL | 总是 | - | 默认条件 |
实现if-else的例子:
arm复制; if(R0 > 10) R1=1; else R1=0;
CMP R0, #10 ; 比较R0和10
MOVGT R1, #1 ; 大于时R1=1
MOVLE R1, #0 ; 小于等于时R1=0
实现while循环的例子:
arm复制; while(R0 != 0) R0--;
loop
CMP R0, #0
BEQ end_loop
SUB R0, R0, #1
B loop
end_loop
4. 栈操作与函数调用
4.1 栈操作指令
ARM使用满递减栈(FD),相关指令:
arm复制STMFD SP!, {R0-R3} ; 压栈(存储多个寄存器)
LDMFD SP!, {R0-R3} ; 出栈(加载多个寄存器)
关键点:
!表示写回,即更新SP值- 寄存器列表顺序不影响实际压栈顺序(总是高编号先压)
- 常用于函数调用时的现场保护
警告:忘记写回(!)是常见错误,会导致后续栈操作覆盖数据。
4.2 ATPCS调用约定
ARM-Thumb过程调用标准规定了寄存器使用规则:
| 寄存器 | 用途 | 保存责任 |
|---|---|---|
| R0-R3 | 参数/返回值 | 调用者 |
| R4-R11 | 局部变量 | 被调用者 |
| R12(IP) | 临时 | 调用者 |
| R13(SP) | 栈指针 | 必须保持 |
| R14(LR) | 返回地址 | 特殊 |
| R15(PC) | 程序计数器 | 不能直接操作 |
参数传递规则:
- 前4个参数通过R0-R3传递
- 更多参数通过栈传递
- 返回值在R0(或R0+R1)
4.3 混合编程实例
C调用汇编函数的例子:
c复制// C声明
extern int asm_add(int a, int b);
// 汇编实现
EXPORT asm_add
asm_add
ADD R0, R0, R1 ; R0 = a + b
BX LR ; 返回
汇编调用C函数的例子:
arm复制MOV R0, #1 ; 参数1
MOV R1, #2 ; 参数2
BL c_add ; 调用C函数
5. 工作模式与异常处理
5.1 工作模式切换
ARM有7种工作模式,通过CPSR切换:
arm复制; 切换到User模式
MRS R0, CPSR ; 读取CPSR
BIC R0, R0, #0x1F ; 清除模式位
ORR R0, R0, #0x10 ; 设置User模式
MSR CPSR_C, R0 ; 写回CPSR
重要:每种模式有独立的SP寄存器,切换后必须重新初始化SP。
5.2 SWI软中断
SWI用于实现系统调用:
arm复制SWI #0x123 ; 触发软中断,编号0x123
中断处理流程:
- CPU切换到SVC模式
- PC跳转到0x08向量地址
- 保存现场,提取SWI编号
- 执行处理程序
- 恢复现场返回
提取SWI编号的方法:
arm复制SUB R0, LR, #4 ; 获取SWI指令地址
LDR R1, [R0] ; 读取指令
BIC R0, R1, #0xFF000000 ; 提取低24位
6. 实际应用经验
6.1 性能优化技巧
- 利用条件执行减少分支:
arm复制CMP R0, #0
ADDGT R1, R1, #1 ; 条件执行,无需分支
-
使用寄存器池:将常用数据保持在寄存器中,减少内存访问。
-
循环展开:对于小循环,手动展开可以减少分支开销。
6.2 常见错误排查
-
栈不平衡:确保每次压栈都有对应的出栈,特别是在有多个返回路径的函数中。
-
寄存器未保存:在调用子函数前,保存好需要保持的寄存器(R4-R11)。
-
立即数非法:遇到非法立即数错误时,改用LDR伪指令或分步加载。
-
模式切换问题:切换模式后忘记设置新模式的SP是最常见的启动错误。
6.3 调试技巧
-
使用模拟器:QEMU等模拟器可以单步执行ARM代码,观察寄存器变化。
-
插入调试指令:在关键位置插入特殊指令(如BKPT)触发调试器。
-
寄存器打印:在异常处理程序中打印寄存器状态,帮助定位问题。
7. 进阶话题
7.1 内联汇编
在C中使用内联汇编的示例:
c复制void delay(unsigned int count) {
__asm {
loop
SUBS count, count, #1
BNE loop
}
}
7.2 向量表配置
正确的异常向量表:
arm复制B reset_handler ; 复位
B undef_handler ; 未定义指令
B swi_handler ; SWI
B pabt_handler ; 预取中止
B dabt_handler ; 数据中止
NOP ; 保留
B irq_handler ; IRQ
B fiq_handler ; FIQ
7.3 内存屏障
在多核或带缓存的系统中,需要内存屏障保证顺序:
arm复制DMB ; 数据内存屏障
DSB ; 数据同步屏障
ISB ; 指令同步屏障
8. 总结与资源推荐
通过本文的讲解,你应该对ARM汇编有了更深入的理解。记住以下几点:
- 理解指令格式和条件执行可以写出更高效的代码
- 严格遵守调用约定是混合编程的关键
- 合理使用栈和寄存器是稳定性的保证
推荐进一步学习的资源:
- 《ARM体系结构与编程》
- ARM官方文档(DDI0406C)
- GNU汇编器手册
- 各种开发板的BSP代码
在实际项目中,建议从简单的功能开始,逐步增加复杂度。遇到问题时,回归基本原理,使用调试工具逐步排查。ARM汇编虽然复杂,但掌握后能让你对系统有更深的理解和控制能力。