3-2-2 ARM架构简明教程:汇编实例分析
1. 目标:深入理解 ARM 汇编
在上一节课里,我们介绍了 ARM 的硬件架构以及基础汇编指令。如果你想更深入地理解 ARM 的底层运行机制,就必须看反汇编代码,即 C 代码被编译后的机器码对应的汇编指令。
今天,我们通过一个简单的 加法函数 来分析它的汇编执行过程,并掌握:
- C 代码如何传递参数给汇编
- 函数调用过程中的寄存器变化
- 栈(Stack)在函数调用中的作用
- 函数返回值是如何传递的
2. 代码准备:创建一个简单的 C 函数
我们写一个简单的 累加函数,并让编译器生成反汇编代码:
int add(int count) {
count = count + 1;
return count;
}
这个函数的作用就是 让 count
加 1,然后返回新的值。接下来,我们通过编译器生成它的反汇编代码,看看它在 ARM 处理器上的实际运行机制。
3. 反汇编代码分析
我们让编译器生成 add
函数的反汇编代码,得到如下指令:
add:
PUSH {LR} ; 1. 保存 LR(链接寄存器,存放返回地址)
ADD R0, R0, #1 ; 2. R0 = R0 + 1 (计算 count + 1)
POP {PC} ; 3. 从栈中取回 PC(返回调用处)
📌 解析:
-
PUSH {LR}
- 进入
add
函数时,我们需要保存返回地址LR
,防止后续跳转丢失。 LR
(Link Register)存放的是函数调用前的地址,用于函数返回。
- 进入
-
ADD R0, R0, #1
R0
里面存放的是count
,这一步就是让count + 1
。
-
POP {PC}
POP
指令从栈中恢复PC
,即恢复LR
,让 CPU 跳回到调用add
函数的代码处。
🔍 总结
- ARM 处理器在函数调用时,参数存放在 R0 - R3 里(最多 4 个参数)。
- 返回值存放在 R0,所以
add
函数返回的值也放在R0
。 - PUSH / POP 保护寄存器,确保函数返回时
PC
仍然指向正确的代码位置。
4. 在主程序中调用 add
假设我们在 main.c
里调用 add
:
void main() {
int result = add(5);
}
主函数的反汇编代码:
MOV R0, #5 ; 1. 把 5 赋值给 R0
BL add ; 2. 跳转到 add 函数(BL = Branch with Link)
MOV R1, R0 ; 3. 把 add 的返回值(R0)存入 R1
📌 分析:
-
MOV R0, #5
- 把参数
5
传递给add
,它存入 R0,因为 ARM 规定 R0 - R3 用于函数参数。
- 把参数
-
BL add
- BL(Branch with Link) 是一个带返回的跳转,即:
- 先把
PC+4
存入LR
- 再跳转到
add
函数
- 先把
- BL(Branch with Link) 是一个带返回的跳转,即:
-
MOV R1, R0
add
的返回值存在R0
里,主程序通过MOV
指令将其存入R1
变量。
5. ARM 栈(Stack)在函数调用中的作用
我们修改 add
,增加一个局部变量:
int add(int count) {
int sum = count + 1;
return sum;
}
现在 sum
是局部变量,那么它存放在哪里?来看新的汇编代码:
add:
PUSH {LR} ; 1. 保护 LR
SUB SP, SP, #4 ; 2. 申请 4 字节栈空间
STR R0, [SP] ; 3. 把 R0 存到栈里(存储 sum)
LDR R0, [SP] ; 4. 读取 sum
ADD R0, R0, #1 ; 5. sum = sum + 1
ADD SP, SP, #4 ; 6. 释放栈空间
POP {PC} ; 7. 恢复 PC,返回主程序
📌 栈的作用
SUB SP, SP, #4
申请 4 字节的栈空间(sum
的存储位置)STR R0, [SP]
把R0
里的count
存入栈LDR R0, [SP]
从栈中读取sum
ADD SP, SP, #4
释放栈空间
你会发现,所有的局部变量都存放在栈(Stack)里。这也是任务切换时为什么要保存栈指针(SP)——因为每个任务的变量都是存放在栈里的!
6. ARM 函数调用的完整流程
-
主程序调用
add(5)
MOV R0, #5
传递参数BL add
进入add
函数,LR 记录返回地址
-
执行
add
PUSH {LR}
保护返回地址SP-4
申请局部变量ADD R0, R0, #1
计算count + 1
POP {PC}
返回主程序
-
主程序获取返回值
MOV R1, R0
存储add
返回的结果
💡 理解这套流程,RTOS 的任务切换就不难了!
7. 代码完整示例
add:
PUSH {LR} ; 保护返回地址
SUB SP, SP, #4 ; 申请 4 字节栈空间
STR R0, [SP] ; 存储 sum
LDR R0, [SP] ; 读取 sum
ADD R0, R0, #1 ; sum = sum + 1
ADD SP, SP, #4 ; 释放栈空间
POP {PC} ; 返回主程序
8. 结论
✅ ARM 的函数参数通过 R0 - R3
传递
✅ 局部变量存放在栈(Stack)里
✅ PUSH / POP 用于保护 LR 和局部变量
✅ 任务切换时必须保存 SP,否则数据丢失
下节课,我们将学习 任务切换的底层实现,看看 RTOS 是如何管理多个任务的!🚀