ARM汇编
Cortex-A7处理器有9种处理模式
模式 | 描述 |
User(USR) | 用户模式,非特权模式,大部分程序运行的时候就处于此模式 |
FIQ | 快速中断模式,进入FIQ中断异常 |
IRQ | 一般中断模式 |
Supervisor(SVC) | 超级管理员模式,特权模式,供操作系统使用 |
Monitor(MON) | 监视模式?这个模式用于安全扩展模式 |
Abort(ABT) | 数据访问终止模式,用于虚拟存储以及存储保护 |
Hyp(HYP) | 超级监视模式?用于虚拟化扩展 |
Undef(UND) | 未定义指令终止模式 |
System(SYS) | 系统模式,用于运行特权级的操作系统任务 |
除了USR用户模式以外,其他8中模式都是特权模式,这几种模式可以通过软件进行任意切换,也可以通过中断或异常来进行切换。
大多数程序都运行在USR模式下,但是USR模式下不能访问系统所有资源,有些资源是受限的。
USR模式是不能直接进行切换的,只能通过异常来完成模式的切换。
每一种异常都有一组寄存器供异常处理程序使用,这样的目的是为了保证在进入异常模式以后,用户模式下的寄存器不会被破环。
Cortex-A的内核寄存器组,注意不是芯片的外设寄存器
ARM架构提供了16个32位的通用寄存器(R0-R15)供软件使用,前15个(R0-R14)可以用作通用的数据存储,R15是程序计数器PC,用来保存将要执行的指令。
ARM还提供了一个当前程序状态寄存器CPSR和一个备份程序状态寄存器SPSR,SPSR寄存器就是CPSR寄存器的备份。
Cortex-A7有9种运行模式,每一种运行模式都有一组与之对应的寄存器组。
每一种模式可见的寄存器包括15个通用寄存器(R0-R14)、一两个程序状态寄存器和一个程序计数器PC。
各个模式拥有的寄存器如下表
图中前的字体是与User模式所共有的寄存器,蓝绿色背景是各个模式所独有的寄存器。
可以看出在所有模式中,地寄存器组(R0-R7)是共享同一组物理寄存器的,只是一些高寄存器组在不同的模式有自己独有的寄存器。
通用寄存器
R0-R15是通用寄存器,通用寄存器分为以下三类:
1.未备份寄存器,即R0-R7
在所有模式下这8个寄存器都是同一个物理寄存器,在不同的模式下,这8个寄存器中的数据就会被破坏。
2.备份寄存器,即R8-R14
每个模式有各自独立的寄存器。其中R14(LR)寄存器在ARM中主要有2个作用:
- 每种处理器模式使用R14(LR)来存放当前子程序的返回地址。
如果使用BL或者BLX来调用子函数的话,R14(LR)被设置成该子函数的返回地址,在子函数中将R14(LR)中的值赋给R15(PC)即可完成函数返回
eg:
MOV PC,LR @寄存器LR中的值赋值给PC,实现跳转
或者可以在子函数的入口处将LR入栈:
PUSH {LR} @将LR寄存器压栈
在子函数最后出栈即可
POP {LR}
- 当异常发生以后,该异常模式对应的R14寄存器被设置成该异常模式将要返回的地址,R14寄存器也可以当作普通的寄存器使用
3.程序计数器PC,即R15
R15保存着当前执行的指令地址加8个字节,这是因为ARM的流水线机制导致的。
ARM处理器3级流水线:取指->译码->执行,这三级流水线循环执行。
比如当前执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出来存放在R15(PC)中。
所以R15指向的是当前正在执行的指令地址再加上2条指令地址。
对于32位的ARM处理器,每条指令占4个字节
所以,R15(PC)值 = 当前执行的程序位置 + 8个字节
程序状态寄存器CPSR
所有的处理器模式都共用一个CPSR物理寄存器,因此CPSR可以在任何模式下被访问。
所有的处理器模式都共用一个CPSR(当前程序状态寄存器)必然会导致冲突,为此,除了User和Sys这两个模式以外,其他的7个模式每个都配备了一个专用的物理状态寄存器SPSR(备份程序状态寄存器),
当特定的异常中断发生时,SPSR寄存器用来保存当前程序状态寄存器CPSR的值,当异常退出以后可以用SPSR中保存的值来恢复CPSR。
N(bit31):当两个补码表示的 有符号整数运算的时候,N=1 表示运算对的结果为负数,N=0表示结果为正数。
Z(bit30):Z=1 表示运算结果为零,Z=0 表示运算结果不为零,对于CMP 指令,Z=1 表示进行比较的两个数大小相等。
C(bit29):在加法指令中,当结果产生了进位,则C=1,表示无符号数运算发生上溢,其它情况下C=0。在减法指令中,当运算中发生借位,则C=0,表示无符号数运算发生下溢,其它情况下C=1。对于包含移位操作的非加/减法运算指令,C 中包含最后一次溢出的位的数值,对于其它非加/减运算指令,C 位的值通常不受影响。
V(bit28):对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1 表示符号位溢出,通常其他位不影响V 位。
Q(bit27):仅ARM v5TE_J 架构支持,表示饱和状态,Q=1 表示累积饱和,Q=0 表示累积不饱和。
IT[1:0](bit26:25):和IT[7:2](bit15:bit10)一起组成IT[7:0],作为IF-THEN 指令执行状态。
J(bit24):仅ARM_v5TE-J 架构支持,J=1 表示处于Jazelle 状态,此位通常和T(bit5)位一起表示当前所使用的指令集,如下表
J | T | 描述 |
0 | 0 | ARM |
0 | 1 | Thumb |
1 | 1 | ThumbEE |
1 | 0 | Jazelle |
GE[3:0](bit19:16):SIMD指令有效,大于或等。
IT[7:2](bit15:10):参考 IT[1:0]。
E(bit9):大小端控制位,E=1表示大端模式,E=0表示小端模式。
A(bit8):禁止异步中断位,A=1表示禁止异步中断。
I(bit7):I=1禁止 IRQ,I=0使能 IRQ。
F(bit6):F=1禁止 FIQ,F=0使能 FIQ。
T(bit5):控制指令执行状态,表明本指令是ARM指令还是Thumb指令,通常和J(bit24)一 起表明指令类型,参考J(bit24)位。
M[4:0]:处理器模式控制位,含义如下表
M[4:0] | 处理器模式 |
10000 | User模式 |
10001 | FIQ模式 |
10010 | IRQ模式 |
10011 | Supervisor(SVC)模式 |
10110 | Monitor(MON)模式 |
10111 | Abort(ABT)模式 |
11010 | Hyp(HYP)模式 |
11011 | Undef(UND)模式 |
11111 | System(SYS)模式 |
ARM寄存器CPSR
https://blog.csdn.net/laviolette/article/details/51376751
ARM条件码
https://wenku.baidu.com/link?url=esOgjNZWMUGWC2xJfEXygyL9FpjNGc_jdkrleNcYaNcULpuTQBE9hPLRrMJ9YhBSeh2KNGU0brk7DPCD2pW95MMSyMJyZPq4xphwK4LHJDW
ARM汇编
GUN汇编 GUN汇编适合所有的架构
每条语句有三个可选部分组成
label: instruction@comment
label即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。
任何以" : "结尾的标识符都可以被识别为一个标号。
instruction即指令,汇编指令或伪指令
@符号表示后面的时注释
1 add: 2 MOVS R0,#0X12 @设置R0=0x12
ARM中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用
用户可以使用.section伪操作来定义一个段,汇编系统预定义了一些段名:
.text | 代码段 |
.data | 初始化的数据段 |
.bss | 未初始化的数据段 |
.rodata | 只读数据段 |
使用.section自己定义段
.section .testsection@定义一个testsection段
汇编程序的默认入口标号是_start 也可以在链接脚本中使用ENTRY来指明其他的入口点
.global _start _start: ldr r0,=0x12 @r0=0x12
上面代码中.global是伪操作,表示_start是一个全局标号
常见伪操作:
.byte | 定义单字节数据 | 比如.byte 0x12 |
.short | 定义双字节数据 | 比如.short 0x1234 |
.long | 定义一个4字节数据 | 比如.long 0x12345678 |
.equ | 赋值语句,格式为:.equ 变量名,表达式 | 比如.equ num,0x12 表示num=0x12 |
.align | 数据字节对齐 | 比如.align 4 表示4字节对齐 |
.end | 表示源文件结束 | |
.global | 定义一个全局符号,格式为:.global symbol | 比如.global_start |
GUN汇编同样也支持函数,函数格式如下:
函数名:
函数体
返回语句 @不是必须的
Cortex-A7常用汇编指令
处理器内部数据传输指令
传递数据常见操作:
1.将数据从一个寄存器传递到另一个寄存器
2.将数据从一个寄存器传递到特殊寄存器,如CPSR和SPSR寄存器
3.将立即数传递到寄存器
指令 | 目的 | 源 | 描述 |
MOV | R0 | R1 | 将R1里面的数据复制到R0中 |
MRS | R0 | CPSR | 将特殊寄存器CPSR里面的数据复制到R0中 |
MSR | CPSR | R1 | 将R1里面的数据复制到特殊寄存器CPSR中 |
1.MOV指令
MOV指令用于将数据从一个寄存器拷贝到另一个寄存器,或者将一个立即数传递到寄存器里面
MOV R0, R1 @将寄存器R1中的数据传递给R0,即R0=R1 MOV R0, #0x12 @将立即数0x12传递给R0寄存器,即R0=0x12
2.MRS指令
MRS指令用于特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用MRS指令
MRS R0, CPSR @将特殊寄存器CPSR里面的数据传递给R0,即R0=CPSR
3.MSR指令
MSR指令和MRS指令刚好相反,MSR指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用MSR
MSR CPSR,R0 @将R0中的数据复制到CPSR中,即CPSR=R0
存储器访问指令
ARM不能直接访问存储器,比如RAM中的数据。
有一些寄存器是RAM类型的,我们用汇编来配置寄存器的时候就需要借助存储器访问指令,一般先将要配置的值写入到Rx(x=0~12)寄存器中,然后借助存储器访问指令将Rx中的数据写入到寄存器中,读取就是相反的过程。
指令 | 描述 |
LDR Rd, [Rn, #offset] | 从存储器Rn+offset的位置读取数据存放到Rd中 |
STR Rd, [Rn, #offset] | 将Rd中的数据写入到存储器中的Rn+offset位置 |
1.LDR指令
LDR主要用于从存储器加载数据到寄存器Rx中,LDR也可以将一个立即数加载到寄存器Rx中,LDR加载立即数的时候要使用=,而不是#
eg:有一个寄存器GPIO1_GDIR,其地址为0x0209C004,读取这个寄存器中的数据方法如下
LDR R0, =0x0209C004 @将寄存器地址0x0209C004加载到R0中,即R0=0x0209C004 LDR R1, [R0] @读取地址0x0209C004中的数据到R1寄存器中
2.STR指令
将数据写入到寄存器中
eg:配置寄存器GPIO1_GDIR的值为0x20000002,方法如下
LDR R0, =0x0209C004 @将寄存器地址0x0209C004加载到R0中,即R0=0x0209C004 LDR R1, =0x20000002 @R1保存要写入到寄存器的值,即R1=0x20000002 STR R1, [R0] @将R1中的值写入到R0中所保存的地址中
LDR和STR都是按照字进行读取和写入的,也就是操作的32为数据,
如果要按照字节、半字节进行操作的话可以在指令LDR后面加上B或H。
eg:按字节操作指令就是LDRB和STRB
按半字操作的指令就是LDRH和STRH
压栈和出栈指令
PUSH和POP是一种多存储和多加载指令,即可以一次操作多个寄存器数据,他们利用当前的栈指针SP来生成地址
指令 | 描述 | 另一种写法 |
PUSH <reg list> | 将寄存器列表存入栈中 | STMFD SP!,{R0~R3,R12} @R0-R3,R12入栈 |
POP <reg list> | 从栈中恢复寄存器列表 | LDMFD SP!,{R0~R3,R12} @R0-R3,R12出栈 |
PUSH {R0~R3, R12} @将R0-R3和R12压栈
栈向下增长
压栈以后的堆栈如图,栈指针SP
再将LR进行压栈后的堆栈模型如图 代码倒着执行
出栈从栈顶(SP)位置开始
POP {LR} @先恢复LR POP {R0~R3,R12} @在恢复R0-R3,R12
STMFD SP!,{R0~R3,R12} @R0-R3,R12入栈
LDMFD SP!,{R0~R3,R12} @R0-R3,R12出栈
STM和LDM是多存储和多加载,可以连续的读写存储器中的多个连续数据,LDR和STR每次只能读写存储器中的一个数据
FD:满递减。
跳转指令
1.直接使用跳转指令B、BL、BX
2.直接向PC寄存器里面写入数据
指令 | 描述 |
B<label> | 跳转到label,如果跳转范围超过了+/-2KB,可以使用 B.W<label> 使用32位版本的跳转指令,这样可以得到较大范围的跳转 |
BX<Rm> | 间接跳转,跳转到存放于Rm中的地址处,并且切换指令集 |
BL<label> | 跳转到标号地址,并将返回地址保存在LR中 |
BLX<Rm> | 结合BX和BL的特点,跳转到Rm指定的地址,并将返回地址保存在LR中,切换指令集 |
1.B指令
B指令会将PC寄存器的值设置为跳转目标地址,一旦执行B指令,处理器会立即跳转到指定的目标地址。 如果要调用的函数不会在返回到原来的执行处则可以使用B指令
代码是典型的在汇编中初始化C运行环境,然后跳转到C文件的main函数中运行
_start: ldr sp,=0X80200000 @设置栈指针 b main @跳转到main函数
2.BL指令
BL指令相比较B指令,在跳转之前会在寄存器LR(R14)中保存当前PC寄存器值,所以可以通过LR寄存器中的值重新加载到PC中继续运行跳转之前的代码。
中断服务函数
push {r0, r1} @保存r0,r1 cps #0x13 @进入SVC模式,允许其他中断再次进入 bl system_irqhandler @加载C语言中断处理函数到r2寄存器中 //c语言中的中断处理函数 cps #0x12 @进入IRQ模式 pop {r0, r1} str r0, 【r1, #0x10】 @中断执行完成,写EOIR
算数运算指令
常用的算数运算指令
指令 | 计算公式 | 备注 |
ADD Rd,Rn,Rm | Rd = Rn + Rm | 加法运算,指令为ADD |
ADD Rd,Rn,#immed | Rd = Rn + #immed | |
ADC Rd,Rn,Rm | Rd = Rn + Rm + 进位 | 带进位的加法运算,指令为ADC |
ADC Rd,Rn,#immed | Rd = Rn + #immed + 进位 | |
SUB Rd,Rn,Rm |
Rd = Rn - Rm |
减法 |
SUB Rd,#immed | Rd = Rd - #immed | |
SUB Rd,Rn,#immed | Rd = Rn - #immed | |
SBC Rd,Rn,#immed | Rd = Rn - #immed - 借位 | 带借位的减法 |
SBC Rd,Rn,Rm | Rd = Rn - Rm - 借位 | |
MUL Rd,Rn,Rm | Rd = Rn * Rm | 乘法(32位) |
UDIV Rd,Rn,Rm | Rd = Rn / Rm | 无符号除法 |
SDIV Rd,Rn,Rm | Rd = Rn / Rm | 有符号除法 |
逻辑运算指令
常用的逻辑运算指令
指令 | 计算公式 | 备注 |
AND Rd,Rn | Rd = Rd & Rn | 按位与 |
AND Rd,Rn,#immed | Rd = Rn & #immed | |
AND Rd,Rn,Rm | Rd = Rn & Rm | |
ORR Rd,Rn | Rd = Rd | Rn | 按位或 |
ORR Rd,Rn,#immed | Rd = Rn | #immed | |
ORR Rd,Rn,Rm | Rd = Rn | Rm | |
BIC Rd,Rn | Rd = Rd & (~Rn) | 位清除 |
BIC Rd,Rn,#immed | Rd = Rn & (~#immed) | |
BIC Rd,Rn,Rm | Rd = Rn & (~Rm) | |
ORN Rd,Rn,#immed | Rd = Rn | (#immed) | 按位或非 |
ORN Rd,Rn,Rm | Rd = Rn | (RM) | |
EOR Rd,Rn | Rd = Rd ^ Rn | 按位异或 |
EOR Rd,Rn,#immed | Rd = Rn ^ #immed | |
EOR Rd,Rn,Rm | Rd = Rn ^ Rm |
1.需要汇编初始化一些SOC外设(三星芯片需要关注看门狗)
2.使用汇编初始化DDR,I.MX6U不需要
3.设置SP指针,一般指向DDR,设置好C语言运行环境
在stm32中,汇编定义了中断函数,startup_stm32fxxxxxx.s
汇编调用函数如何传参: 中断函数为例
xxx_irqhandler是一个C语言函数,中断函数有一个参数,参数是中断号。
汇编调用C语言函数时建议形参不要超过4个,形参可以由r0 - r3这四个寄存器来传递
如果形参大于4个,那么多的部分就要使用堆栈进行传递。
所以给r0寄存器写入中断号就可以完成了给xxx_irqhandler函数传递参数
pc指向的是正在取值的地址 三级流水线:取值、译指、执行, pc = 当前执行指令地址+8
0x2000 MOV R1,R0 执行
0x2004 MOV R2,R3 译指
0x2008 MOV R4,R5 取值 PC
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)