汇编语言的基础使用
本篇简单介绍了关于汇编的一些基础指令的用法,但关于栈、寄存器等问题还未写入,会在后面的时间进行补充,汇编会用的语言基础环境依然是keil 4,但需要有相关支持,其他更多的指令建议大家多查手册来学习
学习核心目标
编写ARM 启动代码,为 C 语言运行搭建基础环境,核心任务包括:
- 初始化异常向量表(处理复位、中断等异常)
- 初始化各工作模式的栈指针寄存器(C 语言函数调用依赖栈)
- 开启 ARM 内核中断允许(配置 CPSR 寄存器)
- 将工作模式切换为User 模式(用户程序运行模式)
- 引导程序进入 C 语言
main
函数执行
一、ARM 汇编基础(格式与伪指令)
1. 汇编程序基本结构(汇编中注释用;)
ARM 汇编通过伪指令指导汇编器工作,非处理器指令,核心结构如下:(注意格式:最前面要加tab键)
AREA reset, CODE, READONLY ; 定义代码段,段名reset(复位向量段),属性只读CODE32 ; 后续指令使用32位ARM指令集(Thumb为16位,需用THUMB伪操作)ENTRY ; 标记程序入口(复位后第一个执行的指令位置); 具体内容END ; 标记程序结束
2. 关键伪指令解析
伪操作 | 作用说明 |
---|---|
AREA | 定义段(代码段 / 数据段 / 栈段),格式:AREA 段名, 段属性, 访问权限 |
CODE32 /THUMB | 指定指令集类型:32 位 ARM 指令集 / 16 位 Thumb 指令集 |
ENTRY | 标记程序入口,一个工程仅一个 ENTRY(复位向量段需包含 ENTRY) |
END | 标记汇编文件结束,汇编器遇到 END 后停止处理 |
二、核心 ARM 指令
1. 数据传送指令(MOV/MVN)
(1)MOV 指令(数据移动 / 赋值)
功能:将立即数或寄存器值传送到目标寄存器(类似 C 语言的 = 赋值)
指令格式:
格式 说明 示例 MOV{S}<c> Rd, #const
立即数传送到 Rd MOV R0, #0x8
(R0=8)MOV{S}<c> Rd, Rm
Rm 值传送到 Rd MOV R1, R0
(R1=R0)MOV{S}<c> Rd, Rm, <shift>
Rm 移位后传送到 Rd(移位量 0-31) MOV R6, R0, LSL #31
(左移 31 位)
关键注意点:
- 移位操作支持
LSL
(逻辑左移)、LSR
(逻辑右移)、ASR
(算术右移)、ROR
(循环右移)、RRX
(带进位循环右移,无需移位量)- 立即数需满足12 位立即数规则<z在SUB指令部分所写>
(2)MVN 指令(按位取反)
- 功能:将立即数或寄存器值按位取反后传送到目标寄存器(类似 C 语言 ~)
- 指令格式:
MVN{S}<c> Rd, #const
/MVN{S}<c> Rd, Rm{, <shift>}
- eg:
MVN R0, #0x0
(R0=0xFFFFFFFF,因为 0x0 取反为全 1)
2. 算术运算指令(ADD/SUB/CMP)
(1)ADD 指令(加法)
功能:两个操作数相加,结果存入目标寄存器(类似 C 语言
+
)指令格式:
格式 说明 示例 ADD{S}<c> Rd, Rn, #const
Rn + 立即数 → Rd ADD R6, R0, #0xF0
ADD{S}<c> Rd, Rn, Rm{, <shift>}
Rn + (Rm 移位后) → Rd ADD R7, R0, R1, LSL #1
(R0+2*R1)注意:无
ADD Rd, #a, #b
格式(C 语言a+b
在编译阶段计算,无需机器指令)
(2)SUB 指令(减法)
功能:两个操作数相减,结果存入目标寄存器(类似 C 语言
-
)指令格式(与 ADD 类似):
SUB{S}<c> Rd, Rn, #const
(Rn - 立即数 → Rd)SUB{S}<c> Rd, Rn, Rm{, <shift>}
(Rn - 移位后的 Rm → Rd)
关键补充:
什么是立即数?
立即数是直接嵌入在指令中的常数,无需从内存、寄存器中读取,CPU 可直接用该常数参与运算,它的核心特点是 “指令自带数据”,能减少内存访问次数,提升指令执行效率。
12 位立即数规则
- 判断标准:一个数展开为 32 位二进制后,存在偶数位循环右移,使移位后高 24 位全 0,低 8 位为有效
imm8
(循环移位次数2N
,N=0~15 -> 2N = 0~30) - 原因:ARM 指令为 32 位,立即数由
4位循环移位量(N)+8位imm8
组成,共 12 位(即imm12
) - eg:
#0x80000000
(二进制1000...0000
)可通过imm8=0x80
、循环移位2*15=30
位得到,属于合法立即数;#0x101
无法通过该规则得到,为非法立即数
- 判断标准:一个数展开为 32 位二进制后,存在偶数位循环右移,使移位后高 24 位全 0,低 8 位为有效
(3)CMP 指令(比较)
- 功能:比较两个数(本质是
SUB
运算,但不保存结果,仅更新 CPSR 标志位)[通过做差得到的正负值来判断二者大小] - 指令格式:
CMP<c> Rn, #const
/CMP<c> Rn, Rm{, <shift>}
- 等价关系:
CMP R0, R1
≡SUBS R0, R1
(S
表示更新标志位) - 用途:配合条件跳转指令(如
BLE
、BGT
)实现分支逻辑
用subs实现指令比较(cmp作用)流程图
条件判断标志NZCV
CPSR寄存器中条件判断标志位
N: 符号标志位:上条指令执行结果最高位bit31为1,则 N = 1, 当结果作为有符号解释时为负值;
Z: 零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
C: 进位标志位:进行无符号解读,如果在加法过程中进位或者减法时没有借位,则为 C = 1,否则 C = 0
V: 溢出标志位:进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数)
条件码:eq ge gt le lt al(无条件执行)
equal:等于
not equal:不等于
3. 位操作指令(BIC/ORR)
(1)BIC 指令(指定位清0)
- 功能:将 Rn 的指定位清 0(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言
Rn & ~mask
) - 指令格式:
BIC{S}<c> Rd, Rn, #const
/BIC{S}<c> Rd, Rn, Rm{, <shift>}
- eg:
MOV R0, #0xFFFFFFFF ; R0=全1 MOV R1, #1 BIC R2, R0, R1, LSL #31 ; R0的bit31清0 → R2=0x7FFFFFFF
(2)ORR 指令(指定位置1)
- 功能:将 Rn 的指定位置 1(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言
Rn | mask
) - 指令格式:
ORR{S}<c> Rd, Rn, #const
/ORR{S}<c> Rd, Rn, Rm{, <shift>}
- eg:
MOV R0, #0x00 ; R0=全0 MOV R1, #1 ORR R8, R0, R1, LSL #31 ; R0的bit31置1 → R8=0x80000000
4. 内存访问指令(LDR)
LDR 指令(加载)
- 功能:从内存地址读取数据到寄存器
- 核心格式:
- 加载立即数地址:
LDR Rd, =const
(伪指令,将 32 位地址存入 Rd,解决MOV Rd, #const
无法处理大地址的问题) - 加载标签地址:
LDR Rd, <label>
(将标签对应的内存地址值存入 Rd)
- 加载立即数地址:
- eg:
LDR SP, =0x40001000
(初始化栈指针为0x40001000
,MOV SP, #0x40001000
会报错,因地址超 12 位立即数范围)
STR (存放指令)
功能: 将寄存器中的数据写入(存储)到指定的内存地址中。
核心格式:STR<c> <Rt>, <addressing_mode>
5. 跳转与函数调用指令(B/BL/BX)
指令 | 功能说明 | 示例 | 用途场景 |
---|---|---|---|
B | 无条件 / 条件跳转,不保存返回地址 | B loop (跳转到 loop 标签) | 循环、简单分支 |
BL | 跳转并保存返回地址到LR (Link Register) | BL func (调用 func 函数) | 函数调用(需返回主调函数) |
BX | 跳转并切换指令集(ARM/Thumb) | BX LR (从 LR 恢复 PC,返回) | 函数返回(等价于MOV PC, LR ) |
- 等价关系:
B fun
≡LDR PC, =fun
(PC 为程序计数器,指向当前执行指令地址 + 8)
三、重要应用
1. 循环实现(对应 C 语言循环结构)
循环三要素
循环结束条件
推动循环趋向终结的语句
循环的循环体
(1)do-while 循环(先执行循环体,再判断条件)
C 语言原码:
int i = 0;
int sum = 0;
do {sum += i;i++;
} while (i <= 100);
汇编实现:
MOV R0, #0 ; R0 = i = 0MOV R1, #0 ; R1 = sum = 0
loopADD R1, R1, R0 ; sum += iADD R0, R0, #1 ; i++CMP R0, #100 ; 比较i和100BLE loop ; 若i<=100,跳回loop(BLE=Branch if Less than or Equal)
(2)while/for 循环(先判断条件,再执行循环体)
C 语言原码
int i = 0;
int sum = 0;
while (i <= 100) {sum += i;i++;
}
汇编实现:
MOV R0, #0 ; R0 = i = 0MOV R1, #0 ; R1 = sum = 0
loopCMP R0, #100 ; 先判断i<=100?BGT finish ; 若i>100,跳至finish(BGT=Branch if Greater Than)ADD R1, R1, R0 ; sum += iADD R0, R0, #1 ; i++B loop ; 跳回loop继续判断
finishB finish ; 死循环(防止程序跑飞)
2. 函数定义与调用(含现场保护)
(1)函数调用核心问题
- 问题 1:被调函数修改主调函数的寄存器,导致数据丢失
- 问题 2:函数嵌套时,
LR
被覆盖,无法正确返回 - 解决方案:栈保护现场(入栈保存寄存器,出栈恢复)
(2)汇编函数调用(嵌套调用)
PRESERVE8 ; 栈8字节对齐,解决C函数调用报错
AREA func_demo, CODE, READONLY
ENTRYmain:
LDR SP, =0x40001000 ; 初始化栈指针(关键:必须先初始化栈)
STMFD SP!, {R0-R12, LR} ; 保护main的现场(寄存器+返回地址)
BL func0 ; 调用func0,LR保存main的返回地址
LDMFD SP!, {R0-R12, LR} ; 恢复main的现场
MOV R3, #300 ;