一、处理器工作模式
ARM有7个基本工作模式:
- User:非特权模式,大部分任务执行在这种模式
- FIQ:当一个高优先级(fast)中断产生时将会进入这种模式
- IRQ:当一个低优先级(normal)中断产生时将会进入这种模式
- Supervisor:当复位或软中断指令执行时将会进入这种模式
- Abort:当存取异常时将会进入这种模式
- Undef:当执行未定义指令时会进入这种模式
- System:便用和User模式相同寄存器集的特权模式
Cortex-A特有模式:
- Monitor:是为了安全而扩展出的用于执行安全监控代码的模式;
也是一种特权模式


Part1
学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:
1、初始化异常向量表;
2、初始化各工作模式的栈指针寄存器;
3、开启arm内核中断允许;
4、将工作模式设置为user模式;
5、完成上述工作后,引导程序进入c语言主函数执行;
因此汇编指令的学习主要是围绕这几个目的展开,主要学习跟上述目的相关的指令。
1.格式
伪操作:它们不是 ARM 处理器实际的指令(如 MOV, ADD 等),而是写给汇编器看的命令,用于指导汇编器如何工作
area reset, code, readonly
code32
entry
end
area: 这是最重要的一个伪操作,用于定义一个段。程序、数据、堆栈等都需要被组织在不同的段中。
reset: 这是你为这个段起的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置。
code: 指定该段的属性为代码,意味着这个段包含可执行的指令。
readonly: 指定该段的属性为只读。对于代码段来说,这通常是默认且必须的。
code32: 表示后续指令使用 32位的 ARM 指令集。
thumb: 表示后续指令使用 16位的 Thumb 指令集。
2.指令
1.mov
MOV{S}<c> <Rd>, #<const>
MOV{S}<c> <Rd>, <Rm>
MOV instruction Canonical form
MOV{S} <Rd>, <Rm>, ASR #<n> ASR{S} <Rd>, <Rm>, #<n>
MOV{S} <Rd>, <Rm>, LSL #<n> LSL{S} <Rd>, <Rm>, #<n>
MOV{S} <Rd>, <Rm>, LSR #<n> LSR{S} <Rd>, <Rm>, #<n>
MOV{S} <Rd>, <Rm>, ROR #<n> ROR{S} <Rd>, <Rm>, #<n>
MOV{S} <Rd>, <Rm>, ASR <Rs> ASR{S} <Rd>, <Rm>, <Rs>
MOV{S} <Rd>, <Rm>, LSL <Rs> LSL{S} <Rd>, <Rm>, <Rs>
MOV{S} <Rd>, <Rm>, LSR <Rs> LSR{S} <Rd>, <Rm>, <Rs>
MOV{S} <Rd>, <Rm>, ROR <Rs> ROR{S} <Rd>, <Rm>, <Rs>
MOV{S} <Rd>, <Rm>, RRX RRX{S} <Rd>, <Rm>
注意 (1)与C语言中的赋值运算对比(左值/右值),利于加深理解
(2)#<n>/<Rs> 取值范围 (0 - 31)
(3)RRX{S}:扩展右移 (不需要移位量)
(4)在计算机中只识别二进制数据,计算机没有有无符号,浮动点等概念;
mov r0, #0x8
mov r1, r0
mov r3, #31
mov r0 #1
mov r6, r0, lsl #31
mov r7, r0, lsl r3
2.add(加法指令)
立即数作为第二操作数: ADD{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: ADD{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
mov r0, #0x0F
mov r1, #0xF0
mov r2, #1
add r6, r0, #0xF0
add r7, r0, r1
add r7, r0, r1, lsl #1
add r8, r0, r1, lsl r2
注意 (1){, <shift>} 其中{}代表可选择,","表示在使用时需要在Rm后添加"," shift 移位量(立即数)
(2) add r0, #3, #2 :为什么没有这种形式,C语言int a = 1 + 2; 编译阶段计算, 不需要在机器指令中体现
3.sub(减法指令)
立即数作为第二操作数: SUB{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: SUB{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
mov r0, #0xFF
mov r1, #0xF0
mov r2, #1
sub r6, r0, #0xF0
sub r7, r0, r1
sub r7, r0, r1, lsl #1
sub r8, r0, r1, lsl r2
以上指令都有立即数作为第二操作数的情况,那么什么是立即数呢?
准确的说这里所指的是12位立即数imm12。先说怎么判断某数是不是12位立即数,12位立即数的条件是:
判断标准:把某个数展开成2进制,该数必须存在一种循环右移(偶数位),使得移位后高24位全0,低8位即为有效imm8;
原因:
0000 (rotate)0000 0000(imm8)
N(0-15) 1000 0001
2N(0-30)
4.ldr(加载指令)
LDR<c> <Rt>, <label>
5.sdr(存放指令)
6.MVN(按位取反移动指令):
MVN{S}<c> <Rd>, #<const>
MVN{S}<c> <Rd>, <Rm>{, <shift>}
MVN{S}<c> <Rd>, <Rm>, <type> <Rs>
7.bic(bit clear):指定位置清0
BIC{S}<c> <Rd>, <Rn>, #<const>
BIC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
BIC{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
mov r0, #0xFFFFFFFF
mov r1, #1
bic r2, r0, r1, lsl #31
bic r3, r0, #(1 << 31)
8.orr(or):指定位置1
ORR{S}<c> <Rd>, <Rn>, #<const>
ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
mov r0, #0x00
mov r1, #1
mov r2, #31
orrs r6, r0, #0x80000000
orrs r7, r0, #(1 << 31)
orrs r8, r0, r1, lsl #31
orrs r9, r0, r1, lsl r2
9.条件判断标志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:不等于
10.cmp(compare):比较指令
CMP<c> <Rn>, #<const>
CMP<c> <Rn>, <Rm>{, <shift>}
CMP<c> <Rn>, <Rm>, <type> <Rs>
cmp r0, r1 <==> subs r0, r1
比较两个数中的最大值
练习:比较获取三个数中最大值
11.b bl bx :(跳转指令)
B<c> <label>
b fun <==> ldr pc, =fun
BL<c> <label>
bl fun
BX<c> <Rm>
bx lr <==> mov pc, lr
Part2
1.循环
(1)循环三要素
循环结束条件
推动循环趋向终结的语句
循环的循环体
(2)do...while(C)
int i = 0;
int sum = 0;
do{
sum += i;
i++;
}while(i <= 100)
mov r0, #0
mov r1, #0
loop
add r1, r1, r0
add r0, r0, #1
cmp r0, #100
ble loop
(3)while(C) for()
int i = 0;
int sum = 0;
while(i <= 100)
{
sum += i;
i++;
}
int sum = 0;
for(int i = 0; i <= 100; i++)
{
sum += i;
}
mov r0, #0
mov r1, #0
loop
cmp r0, #100
bgt finish
add r1, r1, r0
add r0, r0, #1
b loop
finish
b finish
2.函数定义及调用
(1)返回值 函数名(形参列表)
{//函数体
代码块
}
(2)pc => lr and lr =>pc
b main
func
mov r0, #1
mov r1, #2
add r3, r0, r1
bx lr
main
mov r0, #100
mov r1, #200
bl func
mov r3, #300
(3)保护现场/恢复现场(压栈/弹栈)
问题1:函数被调修改主调寄存器,问题二:函数嵌套时,无法正确返回。
(1)栈类型空增 空减 满增 满减(ARM 2440))画图讲解
空栈: *sp = xxxx 满栈: sp++
sp++ *sp = xxxx
增栈: sp += 4 减栈: sp -= 4
(2)栈顶指针寄存器初始化
mov sp, #0x40001000 : 报错非立即数
ldr sp, =0x40001000
魔术棒 -> Target->IRAM1:#0x40000000 size:0x1000
(3)保护现场 :stmfd (store(存储) multiple(多个) full(满) decrease(减少))
STMDB<c> <Rn>{!}, <registers>
<Rn>:栈顶指针寄存器
{!},:入栈出栈后,栈顶指针寄存器自减自增
<registers>:入栈出栈的寄存器列表
(4)恢复现场 :ldmfd (load(加载) multiple(多个) full(满) decrease(减少))
LDMFD<c> <Rn>{!}, <registers>
GNU体系 保护现场、恢复现场由主调完成
b main
func1
mov r0, #10
mov r1, #20
cmp r0, r1
movge r2, r0
movlt r2, r1
bx lr
func0
mov r0, #1
mov r1, #2
add r3, r0, r1
stmfd sp!, {r0-r12, lr}
bl func1
ldmfd sp!, {r0-r12, pc}
;bx lr
main
ldr sp, =0x40001000
mov r0, #100
mov r1, #200
stmfd sp!, {r0-r12, lr}
bl func0
ldmfd sp!, {r0-r12, lr}
mov r3, #300
finish
b finish
end
《《《《《《《《《《《《《《《《《《《《《《《休息》》》》》》》》》》》》》》》》》》》》》》》
(4)在汇编中调用C语言函数
(1) 创建main.c
(2) 声明 extern void c_add(void);
(3) 导入 import c_add; (keil当中要求)
(4) 保护现场 bl函数调用 恢复现场
(5) 解决编译报错:asm.axf: Error: L6238E: start.o(reset) contains invalid call from '~PRES8 (The user did not require code to preserve 8-byte aligment of 8-byte data objects)' function to 'REQ8 (Code was permitted to depend on the 8-byte aligment of 8-byte data items)' function c_add.
asm.axf: Finished: 0 information, 0 warning and 1 error messages.
解决办法:栈对齐伪指令:preserve8 用于确保函数调用时栈指针保持 8 字节对齐
(6) 创建工程自动添加了启动代码报错:
(1)chong建覆盖工程
(2)删除.sct文件
(3)添加 start.s main.c
(4)重设软件配置
(7) 魔术棒 -> Debug -> Use Simulator->Run to main(取消)
(8) 魔术棒 -> Linker -> Use Memory Layout from Taget Dialog(勾选)
(9) 魔术棒 -> Taget -> ROM1 -> Start: 0x0 Size:0x2000
(10)函数传参:
stmfd sp!, {r0-r12, lr}
mov r0, #1
mov r1, #2
mov r2, #3
mov r3, #4
mov r4, #5
stmfd sp!, {r4}
bl c_add
ldmfd sp!, {r4}
ldmfd sp!, {r0-r12, lr}
(5)在C语言中调用汇编
(1)导出 export func1;
(2)声明 extern int func1(int a, int b);
(6)修改工作模式
CPSR M域修改
MRS (read): MRS<c> <Rd>, <spec_reg>
MSR (writ): MSR<c> <spec_reg>, #<const>
MSR<c> <spec_reg>, <Rn>
mrs r0, cpsr
bic r0, r0, #(0x1FF << 0)
orr r0, r0, #(0x10 << 0)
mrs cpsr, r0
(7)异常向量表
软中断异常:swi #7
10、ARM 内核工作模式有哪些,分别是在什么情况下被切换?(同4)
ARM内核(主要指ARMv4-v7架构的经典A/R系列)通常有7种工作模式,主要用于处理异常和提供系统保护。模式的切换主要由硬件自动完成或通过软件指令触发。
模式 | 切换条件/触发场景 |
---|---|
用户模式 (User) | 正常程序执行的非特权模式。应用程序通常运行在此模式下。 |
系统模式 (System) | 一种特权模式,与User模式共用寄存器。通过软件直接修改CPSR的模式位进入,用于运行需要特权的操作系统任务。 |
快速中断模式 (FIQ) | 当处理器接受一个快速中断 (FIQ) 请求时自动进入,用于处理高速数据传输、紧急事件等。 |
普通中断模式 (IRQ) | 当处理器接受一个普通中断 (IRQ) 请求时自动进入,用于处理一般的外设中断。 |
管理模式 (Supervisor) | 复位后的默认模式 。当执行软件中断指令 (SWI/SVC) 或进行软复位时自动进入。这是操作系统的内核模式。 |
中止模式 (Abort) | 当发生内存访问失败 时自动进入,例如:- 预取指中止 :CPU试图从无效地址取指令。- 数据中止:指令试图访问无效的内存数据地址。 |
未定义模式 (Undefined) | 当CPU遇到一条它无法识别(未定义)的指令时自动进入。 |
Cortex-A特有模式: **8.Monitor:**是为了安全而扩展出的用于执行安全监控代码的模式:也是一种特权模式
9.HYP:测试
总结 :除了用户模式 和系统模式 ,其他5种模式都与特定的异常一一对应。发生异常时,硬件自动切换模式;处理完异常后,通过特定的指令恢复回之前的模式。
11、异常向量表是什么?(同5)
异常向量表是内存中一块固定的、连续的区域,其中存放着各种异常处理程序的入口地址(或跳转到处理程序的指令)。
-
工作原理 :当异常发生时,ARM处理器会自动地、硬连线地 根据异常类型,跳转到表中一个固定的偏移地址上。例如,发生复位异常,就跳转到
0x00000000
;发生IRQ中断,就跳转到0x00000018
。 -
核心作用 :它是连接硬件异常 和软件处理程序 的桥梁。硬件通过固定机制自动定位处理程序,确保了异常响应的实时性和确定性。
-
典型布局(地址偏移量):
-
0x00
: 复位 (Reset) -
0x04
: 未定义指令 (Undefined Instruction) -
0x08
: 软件中断 (SWI/SVC) -
0x0C
: 预取指中止 (Prefetch Abort) -
0x10
: 数据中止 (Data Abort) -
0x14
: 保留 (Reserved) -
0x18
: IRQ -
0x1C
: FIQ
-
12、什么是立即数?如何判断某数是非法是12位立即数?
-
立即数 :是指在指令编码本身中直接包含的操作数。执行时可以直接使用,无需从寄存器或内存中额外读取。
- 例如:在指令
MOV R0, #0xFF
中,0xFF
就是一个立即数。
- 例如:在指令
-
判断12位立即数是否合法 :ARM指令中的立即数并非完整的32位,而是由一个12位的编码字段(4位旋转值 + 8位立即数) 来表示。一个32位的常数是合法的12位立即数,当且仅当它可以通过以下方式生成:
一个8位的数值(范围0-255),循环右移偶数位(0, 2, 4, ..., 30)后得到的32位常数。
判断方法:您可以想象这个数能否被一个"字节+移位"的组合所表示。常见的非法立即数有:
-
过大的数(如
0x12345678
) -
所有位模式中若出现连续 1 或连续 0 的"块"长度 > 8 位 ,基本就编不进去,(如
0x00000FFF
是合法的,因为它就是255循环右移0位;而0x000001FF
很可能是非法的)
-
13、b, bl, bx 指令的区别是什么?
这三种都是分支(跳转)指令,但用途不同。
指令 | 全称 | 区别与用途 |
---|---|---|
b | Branch | 单纯跳转 。将程序计数器PC直接设置为目标地址。不保存返回地址,用于无需返回的跳转,如循环、条件分支。 |
bl | Branch with Link | 带链接的跳转 。在跳转之前,自动将下一条指令的地址(返回地址)保存到链接寄存器LR(r14) 。用于函数调用,因为函数结束后需要返回到调用处。 |
bx | Branch and eXchange | 跳转并切换指令集 。跳转到目标寄存器中存储的地址。根据目标地址的最低位(bit[0])来决定后续执行ARM指令(T=0)还是Thumb指令(T=1) 。用于从ARM代码跳转到Thumb代码,以及函数返回(bx lr )。 |
简单总结:
-
b
:跳而不返(用于goto,循环)。 -
bl
:跳而必返(用于调用函数)。 -
bx
:跳并可换(用于状态切换和间接跳转)。
14、ARM内核采用的栈是哪种栈?
-
硬件设计 :ARM内核的压栈(PUSH)操作硬件上只支持向低地址增长(递减)。
-
栈指针特性 :ARM的栈指针SP(r13)指向的是栈顶最后一个被压入的有效数据(满栈)。
因此,ARM内核采用的栈是 满递减栈 (Full Descending Stack, FD)。
-
满 (Full):SP指向栈顶最后一个有效数据。
-
递减 (Descending):栈向内存低地址方向生长。压栈时SP减小,弹栈时SP增大。
补充:四种栈类型理论模型
栈的增长方向和栈指针(SP)的位置共同定义了栈的类型,共有四种组合:
类型 | 简称 | 增长方向 | SP指向 | 压栈(PUSH)操作 | 弹栈(POP)操作 |
---|---|---|---|---|---|
满递减 | FD | 向低地址 | 最后入栈的数据 | 先减SP,再存数据 | 先取数据,再加SP |
空递减 | ED | 向低地址 | 下一个空位置 | 先存数据,再减SP | 先加SP,再取数据 |
满递增 | FA | 向高地址 | 最后入栈的数据 | 先加SP,再存数据 | 先取数据,再减SP |
空递增 | EA | 向高地址 | 下一个空位置 | 先存数据,再加SP | 先减SP,再取数据 |