ARM笔记

ARM基础

零散知识

stm32f103是crotex-m3内核,armv7结构,哈佛结构
s5p6818是crotex-a53内核,armv8架构,哈佛结构

指令集

精简指令集:RISC指令,一条指令占32位内存空间,大多数指令都是单周期。
复杂指令集:CISC指令,指令宽度不固定,指令周期不固定。

注: arm是精简指令集RISC

ARM处理器工作状态

ARM7、ARM9、ARM11 有7个基本工作模式:

用户模式User
非特权模式,大部分任务执行在这种模式

快速中断模式FIQ
当一个高优先级中断产生时将进入这种模式

外部中断模式IRQ
当一个低优先级中断产生时将会进入这种模式

管理模式SVC
当复位或软中断指令执行时将回进入这种模式

中止模式ABT
当存取异常时将会进入这种模式

未定义指令模式UND
当执行未定义指令时会进入这种模式

系统模式SYS
使用和User模式相同寄存器集的特权模式

Cortex-A系列

Cortex-A系列新增Monitor 模式。
Monitor : 是为了安全而扩展出的用于执行安全监控代码的模式;
也是一种特权模式

ARM中的寄存器

ARM7,ARM9,ARM11 有37个32-Bits长的寄存器。
Cortex体系结构下有40个32-Bits长的寄存器
Cortex-A(ARM-v7)多出3个寄存器,Monitor 模式 r13_mon , r14_mon, spsr_mon
在这里插入图片描述
R13:栈指针(sp),sp中存放的值在退出调用函数时必须与进入调用函数时的值相同
R14:链接寄存器(lr),当调用一个函数时或执行中断时,下一条指令的地址(返回值的地址)被自动保存到链接寄存器中。在函数返回时或中断结束时有效
PC:程序计数器,用于存放下一条指令所在单元的地址的地方。取址就是根据PC中存放的的指令的地址来取址。
CPSR:程序状态寄存器,
SPSR:程序状态寄存器,当异常出现时,SPSR用来保存CPSR的值。当异常结束时,再恢复CPSR的值,即程序运行状态

程序状态寄存器

在这里插入图片描述
条件位
[31:28]位,即N,Z,C,V这四位被称为条件标志位,几乎所有指令都会测试CPSR中的条件标志位来决定指令是否执行。
N:符号位
Z:结果为零设置为1,繁殖为0
C:一般有四种情况
1. 对于加法,包括比较指令CMN,如果加法运算产生无符号溢出,C设置为1,反之为0
2. 对于减法,包括比较指令CMP,如果 减法运算产生一个借位,C设置为1,反之为0
3. 对于包含移位操作的非加法运算,C设置为移位器移出的最后一位
4. 对于其他的非减法运算,C通常保持不变
V:一般有两种情况
1. 在两个二进制补码的有符号整型的加法和减法指令运算是,如果发生符号一处,则设置为1
2. 对于非减法和加法指令,V通常是不变的

中断禁止位
[7]:I:为1表示禁止IFQ
[6]:F:为1表示禁止FIQ

处理器模式位
[4:0]
1. 10000:User mode
2. 10001:FIQ mode
3. 10011:SVC mode
4. 10111:Abort mode
5. 11011:Undfined mode
6. 11111:System mode
7. 10010:IRQ

异常处理

中断向量表

异常类型优先级工作模式异常向量地址说明
复位 RESET1管理模式0x00000000复位引脚有效时进入
未定义指令 UND6未定义指令中止模式0x00000004
软件中断 SWI6管理模式0x00000008用户定义的中断指令
指令预取终止 PABT5中止模式0x0000000C预取指令不存在或不允许访问时进入
数据访问中止 DABT2中止模式0x00000010当前数据访问呢指令的目标地址不存在或不允许访问时进入
外部中断请求 IRQ4外部中断模式0x00000018外部中断发生时进入
快速中断请求 FIQ3快速中断模式0x0000001C快速中断发生时进入

注: 中断向量表的地址可能不同,但是顺序一定相同

产生异常时

  1. 拷贝CPSR到 SPSR_< mode >
  2. 设置相应的CPSR
    1. 改变处理器状态进入ARM态
    2. 改变处理器模式,进入相应异常模式
    3. 设置中断禁止位禁止相应中断(如果需要的话)
  3. 保存返回地址到 LR_< mode >
  4. 设置PC为相应的异常向量

从异常返回时

  1. 从CPSR_< mode >恢复到CPSR
  2. 从 LR_< mode > 恢复到PC

三级流水线

目的
提高效率

内容
程序计数器PC保存写一次要取得指令地址。除非有其他情况,否则处理器在每次取指令后总是递增PC,使他能够按顺序取得下一条指令。

一条指令的执行分为三个阶段:取址、译码、执行
取址,即将指令从存储器中取出
译码,即解码指令中用到的寄存器
执行,即处理指令,并将结果写入寄存器

三级流水线如下图:
在这里插入图片描述
注:

  1. PC指向的是被取址的地址,而不是正在执行的指令的地址,PC-8为正在执行的指令的地址
  2. 当一条指令读取pc寄存器的时候,是出于译码阶段,此时,pc指向的是下一条指令的地址

四种栈

根据栈顶指针的位置,栈分为两种:
满堆栈:栈顶指针指向最后插入元素的堆栈
空堆栈:栈顶指针指向限一个要放入元素位置的堆栈

根据堆的生成方式,栈分为两种:
递增堆栈:当栈由低地址向高地址生成时,称为递增堆栈
递减堆栈:当栈由高地址向低地址生成时,称为递减堆栈

上面两种方式混合成四种堆栈:
在这里插入图片描述
分别是:空递增堆栈、满递增堆栈、空递减堆栈、满递减堆栈。

注:ARM是满减栈

汇编指令

常用关键字

关键字含义示例
.text表示下面的程序属于代码段
.global将本文件中的某个程序标号定义为全局的
.include头文件包含
.arm/.code32表示以下为arm指令
.thumb/.code16声明一下为thumb指令
.data定义一个数据码,可读写
.word在存储器中分配4个字节,用指定的数据进行初始化a: .word 2 @分配4个字节空间,并使用2进行初始化
.short在存储器中分配2个字节,用指定的数据进行初始化
.byte在存储器中分配1个字节,用指定的数据进行初始化
.string分配一段字符内存单元str1: .string “abcdef”
.space用value填充size个字节的内存单元buf: .space 16, 1 ;buf: .space(80)

指令格式

一般格式如下:
< opcode > {< cond >} {S} < Rd >, < Rn > {, < op2 > }
其中<>为不可省略,{}为可省略,opcode 、cond与S之间没有分隔符。{S}与Rd之间用空格分开

项目含义备注
< opcode >指令的操作代码即助记符,如MOV、B等
{cond}条件域可不加即可省略条件,如EQ、NE等
{S}指令执行时是否更新CPSR可省略
Rd目的寄存器Rd可以为任意通用寄存器
Rn第一个源操作数Rn可以为任意通用寄存器,且可以与Rd相同
op2第二个源操作数可为寄存器或任意移位寄存器

基本指令

助记符指令功能 描述注意
MOV数据传输指令
LDR存储器到寄存器的数据传送指令LDR R1, =123456 此时立即数前加=,不是#
MVN数据取反传送指令MVN R1, R2 -> R1 = ~R2
CMP比较指令CMP R1, R2 -> CPSR = R1 - R2
CMN比较反值指令CMN R1, R2 -> CPSR = R1 + R2
ADD加法指令
ADC带进位加法指令ADC R1, R2, R2 -> R1 = R2 + R3
SUB减法指令
SBC带借位减法指令SBC R1, R2, R3 -> R1 = R2 - R3 - !C
AND逻辑与指令
ORR逻辑或指令
B跳转指令
BL带返回的跳转指令BL 是另一个跳转指令,但跳转之前,会在寄存器R14 中保存PC 的当前内容,

条件指向及标志位

ARM指令可以通过添加适当的条件码前缀来达到条件执行的目的。

常用条件码如下:

条件码助记符后缀标志含义
0000EQZ置位相等
0001NEZ清零不相等
1011LTN不等于V带符号数小于
1100GTZ清零且N等于V带符号数大于
1101LEZ置位或N不等于V带符号数小于或等于

条件码为CPSR中的N、Z、V、C四位.

其他

机器码
汇编指令经过编译后处理器能够只从的机器指令称为机器码

指令编码
在这里插入图片描述
cond:指令的条件码
I:用于区别operand2是立即数(I=1)还是寄存器(I=0)
opcode:指令操作码
S:操作是否影响cpsr,S=0不影响,S=1影响
Rn:包含第一个操作数的寄存器编码
Rd:目标寄存器编码
operand2:第二操作数,有三种方式:立即数方式,寄存器方式,寄存器移位方式。每种方式下的编码格式不一样

立即数轮回

在MOV指令中,后12位即[11:0]是用来表示立即数的。但是立即数的范围远大于12位,所以[11:0]存储的并不是完整的立即数,而是立即数的一部分。
[11:8]:常数
[7:0]:右移位数的一半。

所以,汇编语言的立即数是通过常数右移得来的。

注:

  1. 当立即数数值在0和0xFF范围时,零immmed_8=< immediate >,rotate_imm=0.、
  2. 其他情况下,汇编编译器选择使rotate_imm数值最小的编码方式。所以0x3f0的正确表示法是第一种。

ARM寻址方式

立即寻址
立即寻址也称为立即数寻址,操作数本身就在指令中给出,只要取出指令也就取到了操作数
例:MOV R1, #0x123

寄存寻址
寄存器寻址就是将寄存器中存储的数值作为操作数。是执行效率最高的一种方式。
例:MOV R1, R2

寄存器间接寻址
寄存器间接寻址就是将寄存器中的值作为操作数的地址,而操作数本山存放在存储器中。用于间接寻址的寄存器必须使用[]括起来。
例:
LDR R1, [R2]
STR R1, [R2]

基址加变址寻址
基址加变址寻址就是将寄存器(一般称作基址寄存器)的内容与指令中给出地址偏移量相加,从而得到一个操作数的有效地址。
例:
LDR R1, [R2 + #4]
STR R1, [R2 + #4]
LDR R1, [R2 + #4]!(加!表示,R2中的内容会改变)
STR R1, [R2 + #4]!(加!表示,R2中的内容会改变)

相对寻址
与基址加变址寻址方式类似,相对寻址以程序计数器(PC值)的当前值作为基址,指令中的地址标号作为偏移量,将两者相加后得到操作数的有效地址。
例:
B stop

堆栈寻址

块拷贝寻址

特殊指令

分支指令注意事项

在这里插入图片描述
分支指令的编码如上图。
其中[23:0]表示偏移量,共计24位,通过换算其可以表示16Mbyte也就是可以跳转的范围是上下8M。
但是,事实上他的跳转范围为上下32Mbyte,也就是64Mbyte,这是什么原因呢?

原因是汇编中指令都是4字节对齐的,所以4字节指令的低两位是没用的,所以跳转指令[32:0]是不包括低两位的。所以换算范围的时候需要乘以4。

LDR伪指令

mov指令可以给寄存器赋一个立即数,但是因为指令长度限制,只能赋值一些特殊的立即数,所以使用起来很不方便。

ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。

LDR伪指令与LDR指令是有区别的。
LDR的伪指令格式为ldr Rn, =expr ,其中Rn为寄存器,expr为立即数。

在这里插入图片描述
LDR汇编指令会被编译器优化。运行时会将立即数作为指令读取到PC中,然后使用 LDR , R1, [PC] 将PC中的值放入寄存器中。而这个指令的位置在为 正在执行指令的地址 + 8
在汇编语言中,会将常量编译在可执行指令后面,通过LDR方式读取,这个存储常量的位置叫做 数据池

移位指令

LSL
左移指令,将无符号操作数左移,低位补零
LSL R1, R0, #3 ----->将R0中数据左移3位放入R1中,但不影响R0中数据。
LSL R1, #3 ----->将R1中数据左移3位,R1中数据改变。

LSLS指令同LSL相同,但会将CPSR中的C设置为移位数据的最高位。
在这里插入图片描述
上图中执行完 LSL R0, #1指令后,CPSR中的C位并没有变化

在这里插入图片描述
上图中,执行完 LSLS R0, #1 后,CPSR中的C位变为1,此时,R0中数据为0x0000003C,最高位为0。
在这里插入图片描述
在上图中,再次经过 LSLS R0, #1 后,CPSR中的值变为0。

LSR
同LSL相似,将无符号操作数右移,高位补0。
同时,若不加S,不影响CPSR中的C位。

ROR
同LSR类似,循环右移,将低位移出位补至高位。
同时,不加S,不影响CPSR中的C位。

ROL
同LSL类似,循环左移,将高位移出位补至低位位。
同时,不加S,不影响CPSR中的C位。

ASR
同LSR类似,将操作数右移,将最高位补至最高位。
同时,不加S,不影响CPSR中的C位。
在这里插入图片描述
在这里插入图片描述
** RRX**
循环右移,使用S补高位。

加载/存储指令

load/store架构规定,存储器之间不能直接拷贝,需要通过寄存器做中转

LDR
从存储器将一个32位的字数据加载至目的寄存器

若想加载半字数,使用LDRH
若想加载字节数据,使用LDRB
若想添加条件位,例LDREQB

使用示例:

指令含义
LDR R0, [R1]将R1指向的存储器中的字数据读取寄存器R0
LDR R4, [R1 + R2]将R1+R2指向的存储器中的字数据读取至寄存器R4
LDR R2, [R1 + #4]将R1+4指向的存储器中的字数据读取至寄存器R2
LDR R1, [R2 + #4]!将R2+4指向的存储器中的字数据读取至寄存器R1,并将新地址写入R1
LDR R1, [R2 + R3]!将R2+R3指向的存储器中的字数据读取至寄存器R1,并将新地址写入R2
LDR R1, [R2], R3将R2指向的存储器中的字数据读取至寄存器R1,并将新地址R2+R3写入R1
LDR R0, [R1, R2, LSL#2]!将由R1+R24指向的存储器中的数据读至寄存器R0,并将R1+R24写入R1,将R2*4写入R2
LDR R0, [R1], R2, LSL#2将R1指向的存储器中的字数据读取至寄存器R1,并将R2*4写入R1

注:不能跨字节读取
在这里插入图片描述

STR
类似于LDR。

批量数据加载/存储指令

批量数据加载指令

LDM{条件} {类型} 基址寄存器 {!}, 寄存器列表 {^}

  1. 其中 {!} 为可选后缀,若选用后缀,则当前数据传送完毕后将最后的地址写入基址寄存器。
  2. 其中 {^} 为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的传送数据外,还将SPSR复制到CPSR。同时该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
  3. 其中类型可选如下表:
类型含义
IA传送后地址增加
IB传送前地址增加
DA传送后地址减小
DB传送前地址减小
  1. 其中参数列表的写法如下表:
写法含义
{R1, R2, R3, R4, R7}R1, R2, R3, R4, R7寄存器,可以是不连续的寄存器
{ R1-R3}R1寄存器到R3寄存器
{R1-R3, R7}R1, R2, R3, R7寄存器

批量数据存储指令
STM同LDM相似。

堆栈指令

注:ARM是满减栈
ARM堆栈操作通过传送指令来完成。
STMFD:入栈(STMDB)
SLMFD:出栈(LDMIA)

指令操作的是R13寄存器,也就是栈指针。

例:
STMFD SP!, {R0-R4}
SLMFD SP!, {R0-R4}
==注:需添加 ! ==

SWP

作用:在寄存器和存储器之间,由一次存储器读和一次存储器写组成的原子操作。完成一个字节或字的交换
原子操作:不可被打断的操作

例:
swp r1, r2, [r0]
将R0指向的内容放入R1,再将R2内容放到R0所指向的空间

PSR传送指令

共包括两条指令:MSR和MRS,主要用于修改CPSR的内容。

MRS用于将CPSR或SPSR中的值传送到另一个寄存器
MSR用于将一个寄存器中的值传送到CPSR或SPSR

注:特权模式可以直接使用PSR传送指令切换到非特权模式,而非特权模式不能使用PSR传送指令直接切换到特权模式,该操作由ARM处理器来完成

SWI指令(软件中断)

格式:swi swi_number
==注:==swi_number范围是0~255

在执行swi时处理器都做了哪些事?

  1. 切换到svc模式
  2. cpsr被分到svc模式下的spsr中
  3. 保存swi下一条指令的地址到svc模式下的lr(r14)中,swi处理结束后从svc模式下lr保存的地址重新取址译码执行
  4. 这是pc到中断向量表相应的入口

异常返回指令

从SWI返回和Undef异常返回
MOVS pc, lr

** 从FIQ、IRQ和预取异常(Perfect Abort)返回**
SUBS pc , lr, #4

从数据异常(Data Abort)返回
SUBS pc, lr, #8

如果LR之前被压栈
使用LDMFD sp!, {sp}^

软中断

在这里插入图片描述

.text
.global _start
_start:
	@中断向量表,中断向量表位置
	b reset
	nop
	b swi_handle
	nop
	nop
	nop
	nop
	nop
	@中断向量表顺序为什么固定?中断向量表为什么一般位于程序开始位置?
	@从设计角度出发,一旦发生异常,从固定的位置固定顺序找中断入口比较合适。

reset:
	ldr sp, =bufend				@设置栈,目的之保护和恢复现场
	@为什么在svc模式下设置栈?
	@因为在不同模式下设置的栈是私有的,swi软中端进入的是svc模式,所以在svc模式下设置栈

	@模式切换
	mrs r0, cpsr
	bic r0, #0x1f
	orr r0, #0x10
	msr cpsr, r0
	@为什么要切换模式?
	@在没有异常产生的情况下,代码应该在非特权模式下运行,即user模式,唯一的非特权模式
	
	ldr r1, =0x11111111
	ldr r2, =0x22222222
	ldr r3, =0x33333333
	ldr r4, =0x44444444
	
	swi 88
	@swi执行会切换到哪个模式下运行?
	@会切换到svc模式下运行
	@在执行swi时处理器都做了哪些事?
	@ 1 切换到svc模式
	@ 2 cpsr被分到svc模式下的spsr中
	@ 3 保存swi下一条指令的地址到svc模式下的lr(r14)中,swi处理结束后从svc模式下lr保存的地址重新取址译码执行
	@ 4 设置pc到中断向量表相应的入口
	@ swi number的范围是0~255
	
	nop
	b stop

swi_handle:
	stmfd sp!, {r0-r12, r14}	@寄存器入栈
	@保护现场,将寄存器值备份到栈空间。因为r0-r12是user与svc模式共有的,svc模式下执行程序可能会修改,所以要备份。而r14寄存器在程序执行过程中,可能会调用bl这种影响r14寄存器的指令,所以也要备份
	
	sub r0, lr, #4				@找到swi指令
	ldr r1, [r0]				@获取指令机器码
	bic r1, #0xff000000			@获取swi number
	cmp r1, #88					@比较机器码
	bleq func					@跳转到相应的函数
	nop
	nop
	ldmfd sp!, {r0-r12, pc}^	@出栈操作
	@入栈时是r14,退栈时给pc赋值,将swi下一条指令地址,给pc,重新取址译码执行
	
func:
	mov r0, #0x12
	mov pc, lr					@函数返回
	@注意通过lr给pc赋值,在返回时从pc处重新取址译码执行
	
stop:

.data
	buf:
		.space (128)
	bufend:
.end

汇编和C语言混合变编程

C语言和汇编混合编程要解决的问题:

  1. 参数传递
  2. 返回值
  3. 调用

返回值:

  1. 若返回值是半精度浮点型,则使用r0的低16位
  2. 小于4个字节的i基本数据类型,扩充为1个字,使用r0返回
  3. 返回字大小的基本数据类型,使用r0返回
  4. 返回双字大小的基本类型数据和64位容器化向量,使用r和r1返回
  5. 128位容器化向量使用0-r3返回

参数:
基本标准规定了在核心寄存器(ro-r3)和堆栈中传递参数。对于带有少量参数的子例程,仅使用寄存器。

添加设置:
在这里插入图片描述

汇编调用C语言

设置: 取消勾选
在这里插入图片描述
汇编代码:

.text
.global _start
_start:
ldr sp, =bufend

mov r0, #2
mov r1, #3
bl fun
nop
nop
nop
b main

.data
	buf:
		.space (128)
	bufend:
.end

C语言代码:

int fun(int a, int b)
{
 return a + b;
}

int main()
{
 return 0;
}

C语言转汇编:
在这里插入图片描述
调用C语言函数过程示意图:在这里插入图片描述

C语言调用汇编

汇编代码:

.text
.global _start
_start:
.global myadd

ldr sp, =bufend
b main

myadd:
	ldr r2, [r0]			@使用R0作为第一个参数
	ldr r3, [r1]			@使用R1作为第二个参数
	add r0, r2, r3	@使用R0作为返回值
	mov pc, lr

.data
	buf:
		.space (128)
	bufend:
.end

C语言代码:

extern int myadd(int, int);//声明是外部的函数
int main()
{
	int a = 10;
	int b = 30;
	myadd(a, b);
	return 0;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值