4-3 ARM 汇编 · 学习笔记
所属课程:韦东山《ARM架构与编程》(百问网 · 单片机核心课程 ARM 架构篇)
所在章节:第 4 章 ARM 芯片基础(本章第 3 节;上节:4-2 ARM内部寄存器)
讲师:韦东山
原视频:4-3_ARM汇编
整理日期:2026-09-29
目录
- 摘要
- 一、本节在课程中的位置
- 二、核心命题
- 三、知识点展开
- [3.1 三套指令集:ARM、Thumb、Thumb-2](#3.1 三套指令集:ARM、Thumb、Thumb-2)
- [3.2 数据处理指令:运算发生在寄存器之间](#3.2 数据处理指令:运算发生在寄存器之间)
- [3.3 内存访问指令:唯一的 Load/Store](#3.3 内存访问指令:唯一的 Load/Store)
- [3.4 跳转指令:控制流的转移](#3.4 跳转指令:控制流的转移)
- [3.5 状态寄存器访问指令](#3.5 状态寄存器访问指令)
- [3.6 综合实例:把 C 语句翻译成汇编](#3.6 综合实例:把 C 语句翻译成汇编)
- 四、常见误区
- 五、本节结论与延伸
- [附录 A:术语速查](#附录 A:术语速查)
- [附录 B:自查清单](#附录 B:自查清单)
摘要
本节讲解 ARM 汇编基础:对比 ARM、Thumb、Thumb-2 指令集------16 位 Thumb 密度高但受限,Thumb-2 混合编码兼得密度与能力。再按数据处理、访存、跳转、状态访问四类梳理指令,以 LDR/ADD/STR 与 BL/BX 演示 4-1 四步运算的汇编形态。
一、本节在课程中的位置
本节是第 4 章第三节,从"认识硬件"进入"用硬件的语言说话":
- 承上:4-1 建立了 RISC"运算在寄存器、访存靠 Load/Store"的模型,4-2 给出了 R0~R12、SP/LR/PC/xPSR 的寄存器词汇表------但两节都停留在"CPU 长什么样"。
- 本节:学习指挥这些寄存器工作的语言------汇编指令,把前三节的地址空间、寄存器、数据通路串成可执行的代码。
- 启下:后续小节将用汇编/反汇编分析 C 语言的内存读写与函数调用过程,并进入异常与中断处理、GNU 汇编工程实践。
一句话定位:汇编是地址空间(4-1)与寄存器(4-2)之间的"操作动词",本节补全裸机编程的最后一块拼图。
二、核心命题
- ARM 有哪几套指令集,该用哪套? ------ 经典 ARM(32 位)、Thumb(16 位)、Thumb-2(16/32 位混合),现代 Cortex-M 内核运行 Thumb-2。
- 常用指令有哪些? ------ 四大类:数据处理、内存访问(Load/Store)、跳转、状态寄存器访问。
- 一条 C 语句落到汇编是什么样子? ------
*p = *p + 1对应"LDR 取数 → ADD 运算 → STR 存回",正是 4-1 四步法则的直接体现。
三、知识点展开
3.1 三套指令集:ARM、Thumb、Thumb-2
| 指令集 | 指令宽度 | 特点 |
|---|---|---|
| ARM | 固定 32 位 | 功能强、寻址灵活,代码密度低 |
| Thumb | 固定 16 位 | 代码密度高(省约 30%~40% 空间),但功能受限:多数指令只能访问 R0~R7,无条件执行、无完整数据处理能力 |
| Thumb-2 | 16/32 位混合 | 兼得密度与能力,Cortex-M3/M4 内核只支持 Thumb-2(无 ARM 状态) |
要点:
- Thumb 是 ARM 指令集的功能子集压缩编码,不是另一种机器;
- Thumb-2 通过指令前缀位区分 16 位与 32 位指令(半字 15:11 为 11101/11110/11111 者为 32 位指令),反汇编器必须逐半字解码;
- Cortex-M 复位后处于 Thumb 状态,T 位恒为 1------这就是 4-2 中"复位向量 bit0 必须为 1"的由来。
3.2 数据处理指令:运算发生在寄存器之间
通用形式:op{S}{cond} Rd, Rn, Operand2------带 S 后缀则更新 APSR 的 N/Z/C/V 标志。
asm
MOV R0, #42 ; R0 = 42 (数据传送)
MVN R0, R1 ; R0 = ~R1 (按位取反传送)
ADD R0, R1, R2 ; R0 = R1 + R2 (加法)
SUB R0, R1, #5 ; R0 = R1 - 5 (减法)
MUL R0, R1, R2 ; R0 = R1 × R2 (低 32 位乘积)
AND R0, R1, R2 ; R0 = R1 & R2 (与)
ORR R0, R1, R2 ; R0 = R1 | R2 (或)
EOR R0, R1, R2 ; R0 = R1 ^ R2 (异或)
BIC R0, R1, R2 ; R0 = R1 & ~R2 (位清除)
LSL R0, R1, #2 ; R0 = R1 << 2 (逻辑左移,另有 LSR/ASR/ROR)
比较与测试指令只改标志、不存结果:
asm
CMP R0, R1 ; 按R0-R1更新标志(Compare)
TST R0, R1 ; 按R0&R1更新标志(Test)
Cortex-M3 的 Thumb-2 还引入除法 SDIV/UDIV 与 CBZ/CBNZ(比较为零跳转)、IT 条件块等增强指令。
3.3 内存访问指令:唯一的 Load/Store
ARM 是 Load/Store 架构,运算指令不能直接操作内存,访存只有两类:
asm
LDR R0, [R1] ; R0 = *R1 (读 32 位字)
LDR R0, [R1, #4] ; R0 = *(R1+4) (立即数偏移)
LDR R0, [R1, R2] ; R0 = *(R1+R2) (寄存器偏移)
LDRB R0, [R1] ; 读单字节;LDRH 读半字
STR R0, [R1] ; *R1 = R0 (写 32 位字)
STRB R0, [R1] ; 写单字节;STRH 写半字
LDM R0, {R1-R5} ; 批量加载(多寄存器)
STM R0, {R1-R5} ; 批量存储
PUSH {R0, LR} ; 压栈(STM 的别名)
POP {R0, PC} ; 出栈(LDM 的别名)
伪指令 LDR Rd, =立即数 用于把任意 32 位常数/地址装入寄存器------这正是 4-1 中"指针指向 0x40010800"的汇编实现方式。
3.4 跳转指令:控制流的转移
asm
B label ; 无条件跳转(Branch)
BEQ label ; 条件跳转:Z=1 则跳(B + 条件码组合)
BL func ; 跳转并链接:LR = 返回地址,PC = func(调用子程序)
BX LR ; 跳转到寄存器地址(从子程序返回)
BLX Rm ; 带链接+状态切换(Cortex-M 中状态恒为 Thumb)
要点:
BL func ... BX LR是子程序调用的标准范式:BL 把返回地址存入 LR(呼应 4-2"LR 省一次访存"的设计),BX LR 跳回;- 也可直接向 PC 赋值实现跳转(
MOV PC, LR); BX/BLX Rm依据 Rm 的 bit0 判断目标状态:为 1 保持 Thumb,为 0 切到 ARM 状态(Cortex-M 上恒为 Thumb,bit0 必须为 1)。
3.5 状态寄存器访问指令
4-2 讲过:xPSR 等特殊寄存器没有内存地址,只能用专用指令访问:
asm
MRS R0, APSR ; 读程序状态寄存器到通用寄存器
MSR APSR, R0 ; 写程序状态寄存器
MRS R0, CONTROL ; 读 CONTROL(MSP/PSP 选择等)
MSR CONTROL, R0
3.6 综合实例:把 C 语句翻译成汇编
C 代码(4-1 的指针访问外设寄存器):
c
unsigned int *p = (unsigned int *)0x40010800;
*p = *p + 1;
对应的 Thumb-2 汇编与四步法则对照:
asm
LDR R1, =0x40010800 ; 伪指令:把寄存器地址装入 R1
LDR R0, [R1] ; ① 读内存:R0 = *p
ADD R0, R0, #1 ; ② CPU 内部运算:R0 = R0 + 1
STR R0, [R1] ; ③ 写回内存:*p = R0
子程序调用完整范式:
asm
BL my_func ; 调用:LR = 返回地址
... ; 返回后继续
my_func:
PUSH {R0, LR} ; 保存现场(多级调用需保护 LR)
... ; 函数体
POP {R0, PC} ; 恢复现场并返回(PC 出栈即跳回)
可以看到:每一条指令都精确落在 4-1(Load/Store、统一编址)与 4-2(寄存器分工、LR/PC)划定的框架之内------汇编不过是这套框架的"操作动词表"。
四、常见误区
- "Cortex-M 上可以写 32 位 ARM 指令" ------ 错。Cortex-M3/M4 只支持 Thumb/Thumb-2,复位即处于 Thumb 状态,T 位恒为 1。
- "Thumb 与 ARM 是两种互不相干的机器语言" ------ 错。Thumb 是 ARM 指令集的压缩编码子集,同一程序可混合链接。
- "
ADD R0, [R1]可以直接对内存做加法" ------ 错。Load/Store 架构下运算指令的操作数只能是寄存器/立即数,必须先 LDR 后 ADD 再 STR。 - "CMP 会把差值存入寄存器" ------ 错。CMP 只按减法结果更新 N/Z/C/V 标志,不保存结果,专供条件跳转使用。
- "BL 调用后必须 PUSH {LR}" ------ 不完全对。一级调用可直接 BX LR 返回;只有嵌套调用(LR 会被覆盖)时才需要压栈保护。
- "xPSR 可以像外设寄存器那样用指针读写" ------ 错。核心寄存器不在地址空间中,只能用 MRS/MSR 指令访问。
五、本节结论与延伸
结论:ARM 汇编 = 三套指令集的选择(现代 Cortex-M 用 Thumb-2)+ 四类动词(数据处理、Load/Store 访存、B/BL/BX 跳转、MRS/MSR 状态访问)。C 语句到汇编的映射严格遵循"先取数入寄存器、再运算、后写回"的 RISC 法则,函数调用则由 BL/BX 与栈共同支撑。
延伸到下一节:掌握了指令动词,下一节将用它们逐行分析 C 程序的运行过程------局部变量在栈上的布局、参数如何经 R0~R3 传递、返回值如何经 R0 返回,从反汇编视角彻底打通 C 与汇编的隔阂。
附录 A:术语速查
| 术语 | 含义 |
|---|---|
| ARM 指令集 | 经典 32 位定长指令集,功能强、密度低 |
| Thumb | 16 位压缩指令集,ARM 子集,密度高、只能访问低寄存器 |
| Thumb-2 | 16/32 位混合编码指令集,Cortex-M3/M4 专用 |
| Load/Store 架构 | 只有取数/存数指令能访存,运算只发生在寄存器间 |
| LDR / STR | 读 / 写内存字指令(B/H 后缀为字节/半字) |
| 伪指令 | 汇编器代为展开的助记符,如 LDR Rd, =imm32 |
| B / BL / BX | 跳转 / 带链接跳转(存返回地址到 LR)/ 跳转到寄存器 |
| CMP / TST | 比较 / 测试:只更新标志位不保存结果 |
| S 后缀 | 指令更新 APSR 的 N/Z/C/V 标志 |
| IT 块 | Thumb-2 的 If-Then 条件执行块,最多 4 条指令免跳转 |
| MRS / MSR | 读 / 写状态寄存器(xPSR、CONTROL 等)的专用指令 |
| PUSH / POP | 压栈 / 出栈(STM/LDM 的栈式别名) |
附录 B:自查清单
- 能说出 ARM、Thumb、Thumb-2 三者关系及 Cortex-M 的选择;
- 能解释 Thumb-2 如何在同一条指令流中混排 16 位与 32 位指令;
- 能默写"读内存→运算→写回"对应的 LDR/ADD/STR 三连;
- 能写出完整的最小子程序:BL 调用、PUSH/POP 保护、BX LR 返回;
- 能说明 CMP 与 SUB、TST 与 AND 的区别(标志 vs 结果);
- 能解释为什么
LDR Rd, =0x40010800需要伪指令而不能用 MOV 一步完成; - 能用 MRS/MSR 读写 xPSR,并说明它与外设寄存器访问方式的差异。