9. ARM汇编基础.md 25 KB

9 ARM汇编基础

9.1 完整概念讲解

9.1.1 为什么需要汇编

Cortex-A 系列芯片上电后,SP 指针未初始化,C 语言运行环境尚未建立,无法直接执行 C 代码。必须先用汇编完成以下工作:

  1. 初始化 DDR 控制器(芯片内部 RAM 不开放给用户代码时)
  2. 设置 SP 指针(栈顶地址)
  3. 初始化其他必要外设

当汇编把 C 环境设置好后,才跳转到 C 语言的 main 函数。这与 STM32 的启动文件 startup_stm32f10x_hd.s 原理相同,只是 STM32 的启动文件由 ST 写好,开发者无需修改。

参考文档

  • 《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition.pdf》— A4 章讲解全部指令
  • 《ARM Cortex-A(armV7)编程手册 V4.0.pdf》— 编程模型与寄存器说明

9.1.2 GNU 汇编语法

MDK 和 IAR 的汇编语法不同,不能直接移植。本教程使用 GCC 交叉编译器,汇编代码必须符合 GNU 语法。

基本格式

label:instruction @comment
  • label:标号,表示地址位置,以 : 结尾
  • instruction:汇编指令或伪操作
  • @:注释符号(也可用 /* */

大小写规则:指令、伪操作、寄存器名可全大写或全小写,但不能混用。

段定义伪操作

伪操作 含义
.text 代码段
.data 已初始化数据段
.bss 未初始化数据段
.rodata 只读数据段
.section 自定义段
.section .testsection  @ 定义自定义段

常用伪操作

伪操作 用法示例 含义
.byte .byte 0x12 定义单字节数据
.short .short 0x1234 定义双字节数据
.long .long 0x12345678 定义 4 字节数据
.equ .equ num, 0x12 赋值(num = 0x12)
.align .align 4 4 字节对齐
.end .end 源文件结束
.global .global _start 定义全局符号

函数格式

函数名:
    函数体
    返回语句       @ 非必须

示例——Cortex-A7 中断服务函数:

/* 未定义中断 */
Undefined_Handler:
    ldr r0, =Undefined_Handler
    bx  r0

/* SVC 中断 */
SVC_Handler:
    ldr r0, =SVC_Handler
    bx  r0

/* 预取终止中断 */
PrefAbort_Handler:
    ldr r0, =PrefAbort_Handler
    bx  r0

9.1.3 数据传输指令

处理器内部数据传输的三种场景:

场景 指令
寄存器 → 寄存器 MOV
特殊寄存器(CPSR/SPSR)→ 通用寄存器 MRS
通用寄存器 → 特殊寄存器 MSR

MOV 指令

将数据从一个寄存器拷贝到另一个寄存器,或加载立即数到寄存器。

MOV R0, R1          @ R0 = R1
MOV R0, #0x12       @ R0 = 0x12

MRS 指令

读取特殊寄存器(CPSR / SPSR)的值到通用寄存器。读特殊寄存器只能用 MRS

MRS R0, CPSR        @ R0 = CPSR

MSR 指令

将通用寄存器的值写入特殊寄存器。写特殊寄存器只能用 MSR

MSR CPSR, R0        @ CPSR = R0

9.1.4 存储加载指令

ARM 不能直接访问存储器(包括寄存器映射的 RAM),需要通过 LDR/STR 指令间接访问。

指令 描述
LDR Rd, [Rn, #offset] 从存储器 Rn+offset 读取数据到 Rd
STR Rd, [Rn, #offset] Rd 的值写入存储器 Rn+offset
LDRB Rd, [Rn, #offset] 按字节读取
STRB Rd, [Rn, #offset] 按字节写入
LDRH Rd, [Rn, #offset] 按半字(16 位)读取
STRH Rd, [Rn, #offset] 按半字写入

LDR 指令详解

LDR 有两种用法:

@ 用法1:从存储器加载数据到寄存器
LDR R0, =0x0209C004      @ 将地址 0x0209C004 加载到 R0(立即数用 =)
LDR R1, [R0]             @ 从地址 0x0209C004 读取数据到 R1

@ 用法2:加载立即数到寄存器
LDR R0, =0x12345678      @ R0 = 0x12345678

关键区别:加载立即数用 =,加载地址值也用 =;加载寄存器中存储的地址指向的数据用 []

STR 指令详解

@ 配置 GPIO1_GDIR 寄存器(地址 0x0209C004)的值为 0x20000002
LDR R0, =0x0209C004      @ R0 = 寄存器地址
LDR R1, =0x20000002      @ R1 = 要写入的值
STR R1, [R0]             @ 将 R1 写入 R0 指向的地址

9.1.5 压栈出栈指令

函数调用时需要保存/恢复寄存器状态(现场保护/恢复现场),使用 PUSH 和 POP 指令。

指令 描述
PUSH <reg list> 将寄存器列表压入栈中
POP <reg list> 从栈中恢复寄存器列表
STMFD SP!, <reg list> PUSH 的另一种写法
LDMFD SP!, <reg list> POP 的另一种写法

压栈示例

@ 将 R0~R3 和 R12 压栈
PUSH {R0~R3, R12}

@ 将 LR 压栈
PUSH {LR}

堆栈变化示意(SP 初始值 0x80000000,向下增长):

高地址
0x80000000  ┌─────────┐
            │  (空)    │ ← 压栈前 SP
0x7FFFFFEC  ├─────────┤
            │   R12    │
            │   R3     │
            │   R2     │
            │   R1     │
            │   R0     │ ← PUSH {R0~R3,R12} 后 SP
0x7FFFFFE8  ├─────────┤
            │   LR     │ ← PUSH {LR} 后 SP
低地址

出栈示例

出栈顺序必须与压栈相反:

POP {LR}                @ 先恢复 LR
POP {R0~R3, R12}        @ 再恢复 R0~R3, R12

STMFD / LDMFD 写法

PUSHPOPSTMFD / LDMFD 的简化写法:

STMFD SP!, {R0~R3, R12}    @ 等价于 PUSH {R0~R3, R12}
STMFD SP!, {LR}

LDMFD SP!, {LR}            @ 等价于 POP {LR}
LDMFD SP!, {R0~R3, R12}

FD = Full Descending(满递减):ARM 使用 FD 类型堆栈,SP 指向最后一个入栈值,堆栈由高地址向低地址增长。STM / LDM 是多存储/多加载指令,可一次操作多个连续数据。

9.1.6 跳转指令

指令 描述
B <label> 跳转到 label(范围 ±2KB,用 B.W 可扩大到 32 位)
BX <Rm> 间接跳转到 Rm 中的地址,并切换指令集
BL <label> 跳转到 label,并将返回地址保存到 LR
BLX <Rm> 跳转到 Rm 指定地址,保存返回地址到 LR,并切换指令集

B 指令——不保存返回地址

用于不需要返回的跳转,例如汇编初始化完成后跳转到 C 的 main 函数:

_start:
    ldr sp, =0x80200000     @ 设置栈指针
    b main                   @ 跳转到 main,不再返回

BL 指令——保存返回地址

用于函数调用,跳转前自动将当前 PC 值保存到 LR(R14):

push {r0, r1}               @ 保存 r0, r1
cps #0x13                   @ 进入 SVC 模式
bl  system_irqhandler       @ 调用 C 语言中断处理函数
cps #0x12                   @ 进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0x10]         @ 写 EOIR,中断处理完成

选择原则:需要返回原处用 BL,跳转后不返回用 B

9.1.7 算术运算指令

指令 公式 说明
ADD Rd, Rn, Rm Rd = Rn + Rm 加法
ADD Rd, Rn, #immed Rd = Rn + #immed 立即数加法
ADC Rd, Rn, Rm Rd = Rn + Rm + C 带进位加法
SUB Rd, Rn, Rm Rd = Rn - Rm 减法
SUB Rd, Rn, #immed Rd = Rn - #immed 立即数减法
SBC Rd, Rn, Rm Rd = Rn - Rm - !C 带借位减法
MUL Rd, Rn, Rm Rd = Rn * Rm 乘法(32位)
UDIV Rd, Rn, Rm Rd = Rn / Rm 无符号除法
SDIV Rd, Rn, Rm Rd = Rn / Rm 有符号除法

嵌入式开发中最常用的是加减指令,乘除基本用不到。

9.1.8 逻辑运算指令

指令 公式 说明
AND Rd, Rn, Rm Rd = Rn & Rm 按位与
AND Rd, Rn, #immed Rd = Rn & #immed 立即数与
ORR Rd, Rn, Rm Rd = Rn \| Rm 按位或
ORR Rd, Rn, #immed Rd = Rn \| #immed 立即数或
BIC Rd, Rn, Rm Rd = Rn & (~Rm) 位清除
BIC Rd, Rn, #immed Rd = Rn & (~#immed) 立即数位清除
ORN Rd, Rn, #immed Rd = Rn \| (~#immed) 按位或非
EOR Rd, Rn, Rm Rd = Rn ^ Rm 按位异或
EOR Rd, Rn, #immed Rd = Rn ^ #immed 立即数异或

逻辑运算指令常用于配置 I.MX6UL 外设寄存器时的位操作。


9.2 核心API/语法

寄存器说明

Cortex-A7 有 16 个 32 位通用寄存器(R0-R15)和 2 个特殊状态寄存器:

寄存器 别名 用途
R0-R3 参数传递 / 返回值(AAPCS 调用约定)
R4-R10 通用变量,被调用者保存
R11 FP 帧指针(可选)
R12 IP 过程间调用暂存
R13 SP 栈指针,指向当前栈顶
R14 LR 链接寄存器,保存子程序返回地址
R15 PC 程序计数器,当前正在执行的指令地址
CPSR 当前程序状态寄存器(标志位、中断使能、处理器模式)
SPSR 保存的程序状态寄存器(异常返回时恢复 CPSR)

CPSR 各位含义

31  30  29  28  ...  7  6  5  4  3  2  1  0
 N   Z   C   V       I  F  T  M4 M3 M2 M1 M0
                                 └──处理器模式──┘
名称 含义
N 负数 运算结果为负置 1
Z 运算结果为零置 1
C 进位 加法进位 / 减法借位
V 溢出 有符号运算溢出置 1
I IRQ 1 = 禁止 IRQ 中断
F FIQ 1 = 禁止 FIQ 中断
T Thumb 0 = ARM 状态,1 = Thumb 状态
M[4:0] 模式 处理器模式(SVC=10011, IRQ=10010 等)

指令速查表

类别 指令 语法
数据传输 MOV / MRS / MSR MOV Rd, Operand2
存储加载 LDR / STR / LDRB / STRB LDR Rd, [Rn, #offset]
压栈出栈 PUSH / POP / STMFD / LDMFD PUSH {reg list}
跳转 B / BL / BX / BLX BL label
算术 ADD / ADC / SUB / SBC ADD Rd, Rn, Rm
乘除 MUL / UDIV / SDIV MUL Rd, Rn, Rm
逻辑 AND / ORR / BIC / EOR AND Rd, Rn, Rm

9.3 代码示例

在线运行:以下所有示例均可直接粘贴到 CPUlator ARM 中运行。 操作:打开网址 → 语言选 ARM → 粘贴代码 → 按 F5 编译加载 → F2 单步执行 → 观察寄存器变化。

示例 1:数据传输指令(MOV / MRS / MSR)

@ 示例1:数据传输指令
@ 在 CPUlator 中运行,观察 R0-R3 和 CPSR 的变化

.global _start

_start:
    @ MOV:寄存器之间拷贝
    mov r0, #0x10           @ R0 = 0x10
    mov r1, r0              @ R1 = R0 = 0x10
    mov r2, #0xff           @ R2 = 0xFF

    @ MOV:加载小立即数
    mov r3, #0x12           @ R3 = 0x12

    @ MRS:读取 CPSR 到通用寄存器
    mrs r0, cpsr            @ R0 = CPSR 的值

    @ MSR:修改 CPSR 的中断禁止位
    orr r0, r0, #0xc0       @ 设置 I=1, F=1(禁止 IRQ 和 FIQ)
    msr cpsr, r0            @ 写回 CPSR

    @ 死循环,程序停在这里
hang:
    b hang

观察要点:单步执行后,R0-R3 的值逐步变化,CPSR 在 MSR 前后发生变化。


示例 2:存储加载指令(LDR / STR)

@ 示例2:存储加载指令
@ 在 CPUlator 中运行,观察寄存器和内存的变化

.global _start

_start:
    @ 用 LDR 加载地址到寄存器
    ldr r0, =0x1000         @ R0 = 0x1000(目标内存地址)

    @ 用 STR 写入数据到内存
    ldr r1, =0x12345678     @ R1 = 0x12345678
    str r1, [r0]            @ 将 R1 写入地址 0x1000

    @ 用 LDR 从内存读取数据
    ldr r2, [r0]            @ R2 = 内存[0x1000] = 0x12345678

    @ 按字节操作(LDRB / STRB)
    ldr r3, =0x2000         @ R3 = 0x2000
    mov r4, #0xAA           @ R4 = 0xAA
    strb r4, [r3]           @ 将 0xAA 写入地址 0x2000(只写低 8 位)
    ldrb r5, [r3]           @ R5 = 0xAA(只读低 8 位)

    @ 按半字操作(LDRH / STRH)
    ldr r6, =0x3000         @ R6 = 0x3000
    ldr r7, =0xBBCC         @ R7 = 0xBBCC
    strh r7, [r6]           @ 将 0xBBCC 写入地址 0x3000(只写低 16 位)
    ldrh r8, [r6]           @ R8 = 0xBBCC

    @ 死循环
hang:
    b hang

观察要点:切换到 Memory 窗口查看地址 0x1000、0x2000、0x3000 的内容变化。


示例 3:算术运算指令(ADD / SUB / MUL)

@ 示例3:算术运算指令
@ 在 CPUlator 中运行,观察标志位(N, Z, C, V)的变化

.global _start

_start:
    @ 加法
    mov r0, #100            @ R0 = 100
    mov r1, #200            @ R1 = 200
    add r2, r0, r1          @ R2 = 100 + 200 = 300

    @ 立即数加法
    add r3, r2, #50         @ R3 = 300 + 50 = 350

    @ 减法
    sub r4, r1, r0          @ R4 = 200 - 100 = 100

    @ 带借位减法(SBC)
    mov r5, #0
    subs r6, r5, #1         @ R6 = 0 - 1 = 0xFFFFFFFF,借位 C=0
    sbc r7, r5, #0          @ R7 = 0 - 0 - !C = 0(因为借位了)

    @ 乘法
    mov r0, #7
    mov r1, #6
    mul r2, r0, r1          @ R2 = 7 * 6 = 42

    @ 无符号除法
    mov r0, #100
    mov r1, #7
    udiv r2, r0, r1         @ R2 = 100 / 7 = 14(商)

    @ 有符号除法
    mov r0, #-100
    mov r1, #7
    sdiv r2, r0, r1         @ R2 = -100 / 7 = -14

    @ 死循环
hang:
    b hang

观察要点:执行 subs 后,CPSR 的 C 位(进位/借位)会变化。


示例 4:逻辑运算指令(AND / ORR / BIC / EOR)

@ 示例4:逻辑运算指令
@ 在 CPUlator 中运行,常用于寄存器位操作

.global _start

_start:
    @ AND:按位与(清除高位)
    ldr r0, =0x1234FFFF     @ R0 = 0x1234FFFF
    and r1, r0, #0x0000FF00 @ R1 = R0 & 0x0000FF00 = 0x0000FF00(只保留 bit8-15)

    @ ORR:按位或(设置某些位)
    mov r2, #0              @ R2 = 0
    orr r2, r2, #0x80       @ R2 = 0x80(设置 bit7)
    orr r2, r2, #0x01       @ R2 = 0x81(设置 bit0)

    @ BIC:位清除(清除指定位)
    ldr r3, =0xFFFFFFFF     @ R3 = 0xFFFFFFFF
    bic r3, r3, #0x01       @ R3 = 0xFFFFFFFE(清除 bit0)
    bic r3, r3, #0x02       @ R3 = 0xFFFFFFFC(清除 bit1)

    @ EOR:按位异或(翻转某些位)
    mov r4, #0xFF           @ R4 = 0xFF
    eor r4, r4, #0x0F       @ R4 = 0xF0(翻转低 4 位)

    @ 实际应用:读取-修改-写入 GPIO 寄存器
    ldr r5, =0x1000         @ 假设 GPIO 寄存器地址
    ldr r6, [r5]            @ 读取当前值
    orr r6, r6, #0x01       @ 设置 bit0(点亮 LED)
    str r6, [r5]            @ 写回

    @ 死循环
hang:
    b hang

观察要点:观察每次逻辑运算后寄存器的二进制位变化。


示例 5:压栈出栈指令(PUSH / POP)

@ 示例5:压栈出栈指令
@ 在 CPUlator 中运行,观察 SP 和栈内存的变化

.global _start

_start:
    @ 设置栈指针(向下增长的满递减栈)
    ldr sp, =0x4000         @ SP = 0x4000(栈顶)

    @ 初始化寄存器
    mov r0, #0x11           @ R0 = 0x11
    mov r1, #0x22           @ R1 = 0x22
    mov r2, #0x33           @ R2 = 0x33
    mov r3, #0x44           @ R3 = 0x44

    @ 压栈:保存 R0-R3
    push {r0, r1, r2, r3}   @ SP 从 0x4000 变为 0x3FF0

    @ 修改寄存器(模拟函数调用)
    mov r0, #0xAA           @ R0 被修改
    mov r1, #0xBB           @ R1 被修改

    @ 出栈:恢复 R0-R3
    pop {r0, r1, r2, r3}    @ SP 从 0x3FF0 变回 0x4000

    @ R0-R3 应恢复为原始值 0x11, 0x22, 0x33, 0x44

    @ STMFD / LDMFD 写法(等价)
    stmfd sp!, {r0-r3}      @ 压栈
    ldmfd sp!, {r0-r3}      @ 出栈

    @ 死循环
hang:
    b hang

观察要点

  1. 执行 push 后 SP 减小(0x4000 → 0x3FF0)
  2. Memory 窗口查看 0x3FF0-0x3FFC 可以看到压入的值
  3. 执行 pop 后 SP 恢复,R0-R3 被还原

示例 6:跳转与比较指令(B / CMP / 条件跳转)

@ 示例6:跳转与比较指令
@ 在 CPUlator 中运行,观察 PC 和条件标志的变化

.global _start

_start:
    @ 计算 1+2+3+...+10 的累加和
    mov r0, #0              @ R0 = sum = 0
    mov r1, #1              @ R1 = i = 1

loop:
    add r0, r0, r1          @ sum = sum + i
    add r1, r1, #1          @ i = i + 1
    cmp r1, #11             @ 比较 i 和 11
    blt loop                @ 如果 i < 11,跳转到 loop

    @ 结束后 R0 = 55(1+2+3+...+10)

    @ BL 跳转示例:调用子程序
    mov r0, #10             @ 参数
    bl  double_value        @ 调用,返回后 R0 = 20
    @ R0 = 20

    @ 死循环
hang:
    b hang

@ 子程序:将 R0 的值翻倍
double_value:
    lsl r0, r0, #1          @ R0 = R0 << 1 = R0 * 2
    bx lr                   @ 返回

观察要点

  1. 单步执行循环,观察 R0(累加和)和 R1(计数器)的变化
  2. cmp 后 CPSR 的 Z 位会变化
  3. blt 条件跳转依赖 CPSR 的 N 位

示例 7:位移操作(LSL / LSR / ASR)

@ 示例7:位移操作
@ 在 CPUlator 中运行,位移是乘除 2 的高效替代

.global _start

_start:
    @ 逻辑左移(LSL):相当于乘以 2^n
    mov r0, #1              @ R0 = 1
    lsl r1, r0, #3          @ R1 = 1 << 3 = 8
    lsl r2, r0, #4          @ R2 = 1 << 4 = 16

    @ 逻辑右移(LSR):相当于无符号除以 2^n
    ldr r3, =0x80           @ R3 = 128
    lsr r4, r3, #3          @ R4 = 128 >> 3 = 16

    @ 算术右移(ASR):有符号除以 2^n(保留符号位)
    mov r5, #-128           @ R5 = -128(0xFFFFFF80)
    asr r6, r5, #3          @ R6 = -128 >> 3 = -16(0xFFFFFFF0)

    @ 循环左移(LSL)实现乘法
    mov r0, #5
    lsl r0, r0, #2          @ R0 = 5 * 4 = 20(左移 2 位 = 乘 4)

    @ 循环右移(LSR)实现除法
    mov r1, #100
    lsr r1, r1, #2          @ R1 = 100 / 4 = 25(右移 2 位 = 除 4)

    @ 死循环
hang:
    b hang

示例 8:综合实战——Cortex-A7 启动汇编框架

以下代码是实际工程中的启动代码片段,需要交叉编译器和链接脚本,无法在 CPUlator 中直接运行。用于理解完整启动流程。

@ Cortex-A7 启动汇编示例(适用于 I.MX6ULL)
@ 文件:start.S

.global _start

_start:
    @ ============================================
    @ 第一阶段:汇编初始化
    @ ============================================

    @ 1. 关闭 IRQ 和 FIQ
    mrs r0, cpsr
    orr r0, r0, #0xc0       @ 设置 I=1, F=1,禁止中断
    msr cpsr, r0

    @ 2. 设置 SVC 模式
    mrs r0, cpsr
    bic r0, r0, #0x1f       @ 清除 M[4:0] 位
    orr r0, r0, #0x13       @ 设置为 SVC 模式(10011)
    msr cpsr, r0

    @ 3. 设置栈指针(SVC 模式栈)
    ldr sp, =0x80200000      @ DDR 起始地址 + 2MB 作为栈顶

    @ 4. 清零 .bss 段
    ldr r0, =__bss_start     @ .bss 起始地址(链接脚本定义)
    ldr r1, =__bss_end       @ .bss 结束地址
    mov r2, #0
bss_loop:
    cmp r0, r1               @ 比较当前地址与结束地址
    bge bss_done             @ 如果 >= 结束地址,跳转完成
    str r2, [r0]             @ 写零
    add r0, r0, #4           @ 地址 +4
    b bss_loop               @ 继续循环
bss_done:

    @ 5. 跳转到 C 语言 main 函数
    bl main                   @ 调用 main,保存返回地址到 LR

    @ main 返回后(理论上不会到这里)
hang:
    b hang                   @ 死循环

中断向量表与跳转

@ 中断向量表(放在代码段最前面)
.section .text.startup, "ax"

.global _vector_table
_vector_table:
    ldr pc, =_start           @ 复位
    ldr pc, =Undefined_Handler
    ldr pc, =SWI_Handler
    ldr pc, =PrefAbort_Handler
    ldr pc, =DataAbort_Handler
    nop                       @ 保留
    ldr pc, =IRQ_Handler
    ldr pc, =FIQ_Handler

@ IRQ 中断服务函数示例
IRQ_Handler:
    push {r0, r1}             @ 保存 r0, r1
    cps #0x13                 @ 切换到 SVC 模式
    bl  system_irqhandler     @ 调用 C 语言中断处理函数
    cps #0x12                 @ 切换回 IRQ 模式
    pop {r0, r1}
    str r0, [r1, #0x10]       @ 写 EOIR 清中断
    subs pc, lr, #4           @ 返回到中断前的下一条指令

函数调用与现场保护

@ 汇编函数示例:调用 C 函数并保存寄存器
my_function:
    push {r4-r11, lr}        @ 保存 callee-saved 寄存器和返回地址

    @ 调用 C 函数
    mov r0, #10               @ 第一个参数
    mov r1, #20               @ 第二个参数
    bl  c_add_func            @ 调用 C 函数,返回值在 r0

    pop {r4-r11, pc}          @ 恢复寄存器并返回

9.4 注意事项与易错点

= 与 # 的区别

LDR R0, =0x12345678    @ 加载立即数到 R0(伪指令,编译器生成文字池)
LDR R0, [R1]           @ 从 R1 指向的地址读取数据
MOV R0, #0x12          @ 加载小立即数(8 位以内)

= 用于加载任意 32 位立即数(编译器自动处理),# 用于直接立即数(有限范围)。

PUSH/POP 顺序必须匹配

@ 错误!出栈顺序与压栈相反
PUSH {r0, r1, r2}
POP  {r2, r1, r0}      @ 正确但不推荐

@ 推荐写法:POP 按原顺序写,硬件自动按正确顺序恢复
PUSH {r0, r1, r2}
POP  {r0, r1, r2}      @ 推荐!

B vs BL 的选择

场景 使用 原因
初始化完成后跳 main B 不需要返回汇编
调用 C 函数 BL 需要返回继续执行
中断处理调用 C 函数 BL 必须返回完成中断恢复

函数返回

  • bx lr:返回到 LR 保存的地址
  • pop {pc}:从栈中恢复 PC(等价于返回)
  • mov pc, lr:简单返回(不切换指令集)

指令大小写

@ 以下两种写法等价
MOV R0, R1
mov r0, r1

@ 但不能混用
Mov r0, R1              @ 错误!

9.5 面试要点

常见问题

Q:为什么 Cortex-A 芯片上电后必须先执行汇编?

A:上电时 SP 指针未初始化,C 语言的函数调用依赖栈(出栈入栈),没有栈 C 语言无法运行。汇编不依赖栈,可以先初始化 DDR、设置 SP,建立 C 运行环境后再跳转到 C 代码。

Q:MOV 和 LDR 加载立即数有什么区别?

A:MOV 只能加载 8 位立即数(通过循环右移偶数位得到的值),LDR 伪指令可以加载任意 32 位立即数(编译器会将值放在代码段附近的文字池中,用 PC 相对寻址加载)。

Q:B 和 BL 指令的区别?

A:B 只做跳转,不保存返回地址;BL 跳转前自动将当前 PC+4(下一条指令地址)保存到 LR 寄存器,支持子程序调用后返回。

Q:PUSH/POP 与 STMFD/LDMFD 的关系?

A:PUSH 等价于 STMFD SP!POP 等价于 LDMFD SP!。FD 表示 Full Descending(满递减堆栈),ARM 处理器默认使用这种堆栈模型。

Q:什么是现场保护?为什么需要它?

A:函数调用或中断发生时,需要先保存当前寄存器值(R0-R15),称为现场保护;函数返回或中断处理完成后,再恢复寄存器值,称为恢复现场。这是保证程序正确执行的基础。

Q:CPSR 寄存器的作用?

A:CPSR(Current Program Status Register)保存当前处理器的状态信息,包括:条件标志位(N/Z/C/V)、中断禁止位(I/F)、处理器模式位(M[4:0])、Thumb 状态位(T)等。通过 MRS/MSR 指令读写。