什么是程序

我认为程序就是

  • 一段被烧写在flash里面的机器码(指令)(bin文件)
  • 运行过程中的数据(属于程序里的一部分)

cpu运行期间会在flash取指令然后执行各种操作。

程序运行时CPU如何读写内存

cpu内部有寄存器,R0~R15寄存器
读数据是将数据从内存RAM读取到寄存器,写数据是将数据从寄存器写入内存RAM。
其中读写操作涉及几条汇编指令:LDR、STR、SUB、ADD、CMP、B、BL、POP、PUSH、LDIMA。
下面我们就来介绍一下这些指令。

ARM汇编简介

了解汇编指令之前我们了解一下什么是汇编语言:

1985年,ARM公司推出了第一款ARM(Acorn RISC Machine)处理器,采用精简指令集(RISC)设计理念。他们发布了两类指令集:ARM指令集和Thumb指令集。ARM指令集是32位指令,Thumb指令集是16位指令。所以基于ARM架构的CPU可以同时运行ARM指令集和Thumb指令集。
那怎么区分当前指令是ARM指令集还是Thumb指令集呢?在程序状态寄存器(CPSR)中,有一个标志位T。当T位为0时,当前指令是ARM指令集;当T位为1时,当前指令是Thumb指令集。
但是指令集只是一串二进制代码,我们怎么区分?所以ARM公司推出了Unified Assembly Language(UAL)即统一汇编语言。所以我们只用记住一些简单的指令,在程序前声明THUMB/ARM指令集之后就可以编写代码了。

LDR读内存

格式为:LDR 目的寄存器,<内存地址>

LDR  R0,[R1]       ;将内存地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将内存地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将内存地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1,R2]! ;将内存地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0,[R1,#8]! ;将内存地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR R0,[R1],R2 ;将内存地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
*/

立即数

MOV R0, #VAL  //  将立即数VAL加载到寄存器R0中

什么是立即数,val可以是任何数吗?

  • 立即数是指在指令中直接给出的数值。ARM汇编中用#前缀表示立即数。
  • 立即数并不是任意 32 位整数,由于 ARM 指令的固定长度(32 位),其中一部分位必须用于表示操作码、目标寄存器等。例如“MOV R0, #VAL”这条指令本身就是16位或者32位指令,哪来的空间保存任意数值的VAL?我们暂且认为立即数满足某种规定。

LDR伪指令

但是我就是想将任意数加载到寄存器中,怎么办?
这就引出了LDR伪指令:LDR R0, =VAL
什么是伪指令?伪指令指的就是不存在的指令,只是编译器在编译时将其转换为实际的指令。例如:

LDR R0, =0X12  
// 0X12是一个立即数,那么编译器会将其转换为MOV R0, #0X12

LDR R0, =0X12345678
/*
0X12345678不是一个立即数
编译器会将其转换为LDR R0, [PC, #offset]
其中这个offset在链接的时候会被计算出来
label DCD 0X12345678 // 在程序的某一个位置存放这个数据
*/

写内存STR

格式为:STR 源寄存器,<内存地址>

STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
STR R1,[R0] ;将R1寄存器的值,传送到以r0为地址的存储器中。

加减SUB、ADD

ADD{S} 目的寄存器,操作数1,操作数2:把两个操作数相加,并将结果存放到目的寄存器中。
操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器,或者一个立即数。

ADD  R0,R1,R2           ; R0 = R1 + R2
ADD R0,R1,#256 ; R0 = R1 + 256
ADD R0,R2,R3,LSL#1 ; R0 = R2 + (R3 << 1)

SUB R0,R1,R2 ;R0 = R1 - R2
SUB R0,R1,#256 ;R0 = R1 - 256
SUB R0,R2,R3,LSL#1 ;R0 = R2 - (R3 << 1)

比较CMP

CMP{条件} 操作数1,操作数2:将一个寄存器的内容和另一个寄存器的内容或立即数进行比较,比较结果放在CPSR中条件标志位的值

CMP R1,R0     ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位

跳转指令

指令 功能描述 应用场景 对寄存器的影响 示例
B 实现无条件跳转,跳转值为相对当前PC的偏移量(24位有符号数扩展为32位) 短距离跳转、循环或条件跳转 仅改变程序计数器(PC) B Label;
CMP R1, #0;
BEQ Label;
BL 跳转前将下一条指令地址存入链接寄存器LR(R14),便于子程序返回 子程序调用 改变PC,将返回地址存入LR BL func
BX 跳转到指定地址,根据目标地址寄存器最低位/两位切换处理器状态(1为Thumb,0为ARM) ARM与Thumb指令集间切换跳转 改变PC,切换处理器状态 BX R0
BLX 结合BL和BX功能,跳转前存返回地址到LR,并切换处理器状态 调用Thumb指令集子程序(调用者为ARM指令集) 改变PC,将返回地址存入LR,切换处理器状态 BLX R0

不了解栈的建议先看这篇博客:内存堆栈管理

POP出栈

内存:
0x2000 0294 = 0x1212 1212
0x2000 0298 = 0xEFEF EFEF
0x2000 029C = 0x0000 2079
0x2000 02A0 = 0x0100 0000

一开始SP指向0x2000 0294这个地址
执行指令:POP {R0 -R3} 之后

R0 = 0x1212 1212
R1 = 0xEFEF EFEF
R2 = 0x0000 2079
R3 = 0x0100 0000

SP指向的位置为: 0x2000 02A4

从上面例子可以看出两点

  • POP指令会将栈顶的数据弹出,并且SP会指向栈顶的下一个地址
  • {R0-R3} 这些高标号的寄存器对应高地址
  • 出栈的时候SP指针从低地址向高地址移动

PUSH压栈

一开始SP指向 0x2000 0284

这个时候寄存器的值:
R0 = 0x1212 1212
R1 = 0x0000 1BC3
R2 = 0x0000 1BC3
R3 = 0x0000 0000

执行指令 PUSH {R0 - R3} 之后,

内存地址:
0x2000 0274的值为: 0x1212 1212 --------- R0
0x2000 0278的值为: 0x0000 1BC3 --------- R1
0x2000 027C的值为: 0x0000 1BC3 --------- R2
0x2000 0280的值为: 0x0000 0000 --------- R3

执行之后 堆栈指针,指向的地址为: 0x2000 0274

从上面例子可以看出两点

  • PUSH指令会将寄存器中的数据压栈,并且SP会指向栈顶的下一个地址
  • {R0-R3} 这些高标号的寄存器对应高地址
  • 入栈的时候SP指针从高地址向低地址移动