ARM学习笔记(二)——ARM汇编基础

一、ARM汇编学习目的

学习 ARM 汇编的主要目的是为了理解和编写 ARM 启动代码

处理器复位以后,需要通过启动代码完成最基本的系统初始化,然后引导程序进入 C 语言环境运行。

启动代码的主要任务包括:

  1. 初始化异常向量表;

  2. 初始化各工作模式的栈指针寄存器;

  3. 开启 ARM 内核中断允许;

  4. 将工作模式设置为 User 模式;

  5. 完成初始化后,引导程序进入 main() 函数执行。

因此,ARM 汇编指令的学习主要围绕启动代码和处理器基本运行过程展开。


二、ARM汇编程序基本格式

ARM 汇编程序中既包含处理器真正执行的指令 ,也包含用于指导汇编器工作的伪操作

1. 伪操作

伪操作不是 ARM 处理器实际执行的指令,而是用于告诉汇编器应该如何组织和处理程序。

例如:

复制代码
AREA reset, CODE, READONLY
CODE32
ENTRY

END

2. AREA

复制代码
AREA reset, CODE, READONLY

AREA 用于定义一个程序段。

其中:

  • reset:段名称;

  • CODE:表示代码段;

  • READONLY:表示只读属性。

程序、数据等内容需要按照不同功能组织在不同的段中。


3. CODE32

复制代码
CODE32

表示后面的程序使用 32位 ARM 指令集


4. THUMB

复制代码
THUMB

表示后面的程序使用 Thumb 指令集。

因此:

复制代码
CODE32 → ARM指令集
THUMB  → Thumb指令集

5. ENTRY

复制代码
ENTRY

表示指定程序的入口位置。


6. END

复制代码
END

表示汇编源程序结束。


三、MOV数据传送指令

MOV 主要用于数据传送。

基本形式:

复制代码
MOV Rd, #const
MOV Rd, Rm

例如:

复制代码
MOV r0, #0x8
MOV r1, r0
MOV r3, #31

执行后:

复制代码
r0 = 8
r1 = r0
r3 = 31

可以将它理解为 C 语言中的赋值操作。


1. MOV与移位

MOV 指令可以配合移位操作:

复制代码
MOV r6, r0, LSL #31
MOV r7, r0, LSL r3

常见移位方式:

指令 含义
LSL 逻辑左移
LSR 逻辑右移
ASR 算术右移
ROR 循环右移
RRX 扩展右移

移位量通常为:

复制代码
0~31

2. RRX

RRX 是扩展右移,不需要单独指定移位量。

它会结合 CPSR 中的 C 标志位进行操作。


四、ADD加法指令

ADD 用于完成加法运算。

基本形式:

复制代码
ADD Rd, Rn, #const
ADD Rd, Rn, Rm
ADD Rd, Rn, Rm, shift

例如:

复制代码
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

例如:

复制代码
ADD r7, r0, r1, LSL #1

可以理解为:

复制代码
r7 = r0 + (r1 << 1)

ARM 指令可以直接对操作数进行移位后再参与运算。


五、SUB减法指令

SUB 用于完成减法运算。

基本形式:

复制代码
SUB Rd, Rn, #const
SUB Rd, Rn, Rm
SUB Rd, Rn, Rm, shift

例如:

复制代码
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

例如:

复制代码
SUB r7, r0, r1

相当于:

复制代码
r7 = r0 - r1

六、ARM中的立即数

ARM 指令中的立即数并不是任意一个 32 位数都可以直接使用。

这里主要涉及 12位立即数 imm12

判断一个数是否能够作为合法的 12 位立即数,可以采用以下方法:

  1. 将该数转换成二进制;

  2. 判断是否存在一种偶数位循环右移

  3. 循环右移后,高24位全部为0;

  4. 低8位能够表示有效的 imm8

可以理解为:

复制代码
12位立即数
   ↓
4位 rotate + 8位 imm8

因此,下面这种形式:

复制代码
MOV r0, #某个数

并不是任意 32 位数都能够直接编码。


七、LDR加载指令

LDR 是加载指令,主要用于将数据从内存加载到寄存器。

例如:

复制代码
LDR sp, =0x40001000

表示将:

复制代码
0x40001000

加载到 sp 中。

由于某些立即数不能通过 MOV 直接表示:

复制代码
MOV sp, #0x40001000

可能无法使用,因此可以采用:

复制代码
LDR sp, =0x40001000

八、STR存储指令

STR 是存储指令,用于将寄存器中的数据写入内存。

可以简单记忆:

复制代码
LDR
内存 → 寄存器

STR
寄存器 → 内存

九、MVN按位取反指令

MVN 用于对操作数进行按位取反。

基本形式:

复制代码
MVN Rd, Rm

例如:

复制代码
MVN r0, r1

表示:

复制代码
r0 = ~r1

每一位都进行取反:

复制代码
0 → 1
1 → 0

十、BIC位清零指令

BIC:

Bit Clear,位清除。

作用:

将指定位置清零。

基本形式:

复制代码
BIC Rd, Rn, Rm

例如:

复制代码
MOV r0, #0xFFFFFFFF
MOV r1, #1

BIC r2, r0, r1, LSL #31

这里:

复制代码
r1 << 31

可以得到:

复制代码
10000000 00000000 00000000 00000000

然后使用 BIC 将 r0 对应位置清零。

因此:

复制代码
BIC → 指定位清0

十一、ORR位置1指令

ORR 是按位或指令。

主要作用:

将指定位置置1。

例如:

复制代码
MOV r0, #0x00
MOV r1, #1

ORR r8, r0, r1, LSL #31

可以将第31位置为1。

因此:

复制代码
BIC → 指定位清0
ORR → 指定位清1

这两个指令在修改寄存器中的某一位时非常常用。


十二、CPSR条件标志位

CPSR 中包含用于条件判断的标志位:

复制代码
N Z C V

分别表示:

复制代码
N → Negative
Z → Zero
C → Carry
V → Overflow

1. N标志位

N 是符号标志位。

如果运算结果最高位:

复制代码
bit31 = 1

则:

复制代码
N = 1

当结果按照有符号数解释时,通常表示结果为负数。


2. Z标志位

Z 是零标志位。

如果运算结果为:

复制代码
0

则:

复制代码
Z = 1

也就是结果的 bit0~bit31 全部为0。


3. C标志位

C 是进位标志位。

对于无符号运算:

加法

如果最高位向更高位产生进位:

复制代码
C = 1

减法

如果减法过程中没有借位:

复制代码
C = 1

4. V标志位

V 是溢出标志位,用于有符号数运算。

例如:

复制代码
两个正数相加
结果变成负数

或者:

复制代码
两个负数相加
结果变成正数

说明发生了有符号溢出:

复制代码
V = 1

十三、ARM条件码

ARM 指令可以根据 CPSR 中的条件标志位决定是否执行。

常见条件码:

复制代码
EQ
GE
GT
LE
LT
AL

其中:

复制代码
EQ → Equal,等于

例如:

复制代码
CMP r0, r1
MOVEQ r2, #1

表示比较 r0r1,如果两个数相等,则执行:

复制代码
MOV r2, #1

AL 表示无条件执行。


十四、CMP比较指令

CMP:

Compare,比较。

基本形式:

复制代码
CMP Rn, #const
CMP Rn, Rm
CMP Rn, Rm, shift

例如:

复制代码
CMP r0, r1

可以理解为进行:

复制代码
r0 - r1

但是计算结果一般不会保存到普通寄存器中,而是根据比较结果修改 CPSR 中的条件标志位。

因此:

复制代码
CMP
 ↓
比较两个数
 ↓
修改NZCV
 ↓
配合条件码进行判断

十五、利用CMP比较两个数的大小

例如:

复制代码
CMP r0, r1
MOVGE r2, r0
MOVLT r2, r1

执行过程:

复制代码
比较 r0 和 r1
      ↓
 ┌────┴────┐
 ↓         ↓
r0 >= r1   r0 < r1
 ↓         ↓
r2 = r0    r2 = r1

最终:

复制代码
r2 = max(r0, r1)

因此可以利用:

复制代码
CMP + 条件码

实现类似 C 语言中的条件判断。


十六、B、BL、BX跳转指令

ARM 中常见的跳转指令有:

复制代码
B
BL
BX

1. B

B:

Branch,跳转。

例如:

复制代码
B fun

表示直接跳转到 fun

主要用于改变程序执行流程。


2. BL

BL:

Branch with Link,带链接的跳转。

例如:

复制代码
BL fun

主要用于函数调用。

执行 BL 时,会将返回位置保存到:

复制代码
LR

即:

复制代码
R14 = LR

因此:

复制代码
BL
 ↓
调用函数
 ↓
保存返回地址

3. BX

BX:

Branch and Exchange。

函数返回时经常使用:

复制代码
BX LR

可以理解为:

复制代码
PC = LR

即根据 LR 中保存的返回地址返回到原来的执行位置。


十七、循环结构

C语言中的循环通常包含三个基本要素:

  1. 循环结束条件;

  2. 推动循环趋向结束的语句;

  3. 循环体。

例如:

复制代码
int i = 0;
int sum = 0;

while(i <= 100)
{
    sum += i;
    i++;
}

可以使用 ARM 汇编实现:

复制代码
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

其中:

复制代码
r0 → i
r1 → sum

执行过程:

复制代码
初始化
 ↓
比较条件
 ↓
是否满足?
 ↓
是
 ↓
执行循环体
 ↓
修改循环变量
 ↓
重新判断

因此,C语言中的 whilefor 等循环,本质上都可以通过:

复制代码
CMP + 条件跳转 + B

实现。


十八、ARM函数调用

ARM 汇编中,函数调用主要通过:

复制代码
BL
+
LR
+
BX LR

完成。

例如:

复制代码
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

执行过程:

复制代码
main
 ↓
BL func
 ↓
进入func
 ↓
执行函数
 ↓
BX LR
 ↓
返回main

十九、为什么需要保护现场

在函数调用过程中,如果被调用函数修改了原来寄存器中的数据,就可能影响调用者后面的程序执行。

另外,当出现函数嵌套调用时:

复制代码
main
 ↓
func0
 ↓
func1

返回地址可能被覆盖。

因此需要对重要寄存器进行:

复制代码
保护现场
 ↓
压栈
 ↓
调用函数
 ↓
恢复现场
 ↓
弹栈

二十、ARM中的栈

栈主要用于:

  • 保存寄存器;

  • 保存函数调用现场;

  • 保存返回地址;

  • 保存函数运行过程中需要的数据;

  • 支持函数嵌套调用。

常见的栈类型有:

复制代码
空增栈
空减栈
满增栈
满减栈

分别对应:

复制代码
Empty / Full
Ascending / Descending

二十一、SP栈指针

ARM 中:

复制代码
R13 = SP

SP:

Stack Pointer,栈指针。

它用于指向当前栈的位置。

程序运行前,需要先对 SP 进行初始化。

例如:

复制代码
LDR sp, =0x40001000

表示:

复制代码
SP = 0x40001000

二十二、STMFD保护现场

保护现场可以使用:

复制代码
STMFD sp!, {r0-r12, lr}

STMFD:

Store Multiple Full Descending

表示按照满递减栈的方式,将多个寄存器保存到栈中。

其中:

复制代码
sp
↓
栈顶指针

!
↓
操作完成后自动更新SP

{r0-r12, lr}
↓
需要保存的寄存器

二十三、LDMFD恢复现场

恢复现场可以使用:

复制代码
LDMFD sp!, {r0-r12, pc}

LDMFD:

Load Multiple Full Descending

用于从栈中恢复多个寄存器。

典型过程:

复制代码
STMFD sp!, {r0-r12, lr}
BL func1
LDMFD sp!, {r0-r12, pc}

整体过程:

复制代码
保存寄存器
   ↓
调用函数
   ↓
执行函数
   ↓
恢复寄存器
   ↓
恢复PC
   ↓
返回

二十四、ARM工作模式修改

ARM 处理器具有不同的工作模式。

修改工作模式时,需要修改:

复制代码
CPSR

主要使用:

复制代码
MRS
MSR

1. MRS

MRS 用于读取状态寄存器。

例如:

复制代码
MRS r0, CPSR

表示:

复制代码
CPSR → r0

2. MSR

MSR 用于写入状态寄存器。

例如:

复制代码
MSR CPSR, r0

表示:

复制代码
r0 → CPSR

3. 修改CPSR模式位

例如:

复制代码
MRS r0, CPSR
BIC r0, r0, #(0x1FF << 0)
ORR r0, r0, #(0x10 << 0)
MSR CPSR, r0

基本过程:

复制代码
读取CPSR
 ↓
清除原来的模式相关位
 ↓
设置新的模式位
 ↓
写回CPSR

二十五、异常向量表

异常向量表是 ARM 启动代码中的重要组成部分。

当处理器发生异常时,需要根据异常类型找到对应的异常处理入口。

例如软件可以通过:

复制代码
SWI #7

产生软件中断异常。

基本过程:

复制代码
发生异常
 ↓
找到对应异常向量
 ↓
进入异常处理程序
 ↓
执行异常处理

因此,在处理器启动过程中,需要正确建立异常向量表。


二十六、ARM启动代码整体流程

ARM 启动代码的基本流程:

复制代码
处理器复位
    ↓
初始化异常向量表
    ↓
初始化各工作模式SP
    ↓
开启中断允许
    ↓
设置User工作模式
    ↓
建立基本运行环境
    ↓
进入main()
    ↓
执行C语言程序

处理器复位之后,程序是如何从最底层的启动代码逐步进入正常程序运行状态的。


二十七、重点指令总结

指令 作用
MOV 数据传送
ADD 加法
SUB 减法
LDR 从内存加载数据
STR 向内存存储数据
MVN 按位取反
BIC 指定位清0
ORR 指定位清1
CMP 比较
B 直接跳转
BL 函数调用
BX 寄存器跳转,常用于函数返回
MRS 读取状态寄存器
MSR 写入状态寄存器
STMFD 多寄存器入栈
LDMFD 多寄存器出栈
相关推荐
wechatbot8885 小时前
【极客互动】企业微信Spring Boot 调度 SearchContact开发自动添加外部联系人网关
汇编·微信·自动化·企业微信·rpa
朝发如雪18 小时前
快速掌握单片机基础(6)(数据处理与传送指令)(跳转与分支指令)(栈操作与混合编程)(程序状态寄存器操作指令)(内存映射)
单片机·arm
晊晌_h1 天前
ARM 学习 |ARM 汇编指令实战笔记
嵌入式·arm·arm汇编
ARM&开发(Haidong)1 天前
ADRV9026 Linux驱动文档详解
arm开发
斑马1391 天前
ARM学习笔记(一)——基本概念
arm开发·arm
斑马1391 天前
ARM学习笔记(三)——i.MX6ULL—LED点灯
arm开发
神威难绷泪1 天前
arm day3 汇编LED / C语言LED
arm
zhangzhangkeji1 天前
stm32 参考文档的目录结构 - 3:STM32F10xxx参考手册(中文).pdf,700多页
stm32·arm
Awh-1 天前
ARM 裸机开发 day02 学习笔记
linux·arm开发·arm