By: Ailson Jack
Date: 2023.10.07
个人博客:http://www.only2fire.com/
本文在我博客的地址是:http://www.only2fire.com/archives/158.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。
微信公众号:
嵌入式那些事
对于搞嵌入式驱动或者操作系统的人来说,掌握汇编语言的使用还是比较重要的,毕竟有时候在分析定位问题的时候,多多少少都会有汇编的身影。本文主要讲讲ARM指令集格式以及常用的ARM汇编指令(主要包括LDR
和STR
指令,LDM
和STM
指令,push
和pop
指令,MOV
指令,CPS
指令,MRS
和MSR
指令,MRC
和MCR
指令,其余指令暂时没列出来,用到时可以查看ARM手册进行了解)。
ARMv7架构是一个32位的处理器架构。同时ARM架构是一个加载/存储体系结构,所有的数据处理操作需要在通用寄存器中完成。
要学习了解处理器的汇编指令,那么首先可以看看汇编指令的通用表达式,具体的指令也就是使用具体的指令和参数代替通用表达式的参数。ARM指令集的指令表达如下所示:
opcode{<cond>}{S} <Rd>, <Rn> {, <Rm>}
opcode
:指令助记符,比如LDR,STR,MOV等。
{}
:大括号括起来的内容表示可选。
<>
:<>
括起来的内容是必须的。
cond
:条件码,比如EQ,NE,CS等,条件码的内容如下图所示:
S
:可选的后缀,如果指令中添加了S
,那么指令的执行结果将会影响到CPSR寄存器的标志位域。
Rd
:目标寄存器。
Rn
:第一个操作数寄存器。
Rm
:第二个操作数寄存器。
在了解了ARM指令的表达式之后,下面就讲讲常用的汇编指令。
LDR
指令用于从内存中读取数据存储到通用寄存器中。STR
指令用于将通用寄存器中的值存储到内存中。LDR
指令的语法如下所示:
LDR{type}{T}{cond} Rt, [Rn {, #offset}]
LDR{type}{cond} Rt, [Rn, #offset]!
LDR{type}{T}{cond} Rt, [Rn], #offset
LDR{type}{cond} Rt, [Rn, +/-Rm]
LDR{type}{cond} Rt, [Rn, +/-Rm]!
LDR{type}{T}{cond} Rt, [Rn], +/-Rm
STR
指令的语法如下所示:
STR{type}{T}{cond} Rt, [Rn {, #offset}]
STR{type}{cond} Rt, [Rn, #offset]!
STR{type}{T}{cond} Rt, [Rn], #offset
STR{type}{cond} Rt, [Rn, +/-Rm]
STR{type}{cond} Rt, [Rn, +/-Rm]!
STR{type}{T}{cond} Rt, [Rn], +/-Rm
type
:操作的数据宽度,可以是:B
(unsigned byte),SB
(signed byte),H
(unsigned halfword),SH
(signed halfword)。
cond
:条件码。
Rt
:目标寄存器。
Rn
:存储内存操作基地址的寄存器。
Rm
:存储偏移量的寄存器。
offset
:立即数。
!
:如果存在,表示最终的地址要写回Rn
。
T
:表示处理器是在用户模式下访问内存地址。
加载存储指令有4种寻址方式,LDR的操作描述如下(STR指令的操作类似):
LDR Rt, [Rn, Op2]
。偏移量Op2
可以是正数或者是负数,可以是一个立即数,可以是另一个寄存器的值,可以是另一个寄存器中的数据进行移位之后的值。LDR Rt, [Rn, Op2]!
。该寻址模式和前变基寻址一样,只是在访问完内存之后Rn寄存器中的值就更新为运算之后得到的新内存地址的值。LDR Rt, [Rn], #offset
和LDR Rt, [Rn], +/-Rm
。将寄存器Rn中存储的数值作为内存地址,将该内存地址中的数据读出来存储到Rt寄存器中,然后将内存地址加减立即数offset或者Rm寄存器中的数值得到新的内存地址存储到Rn寄存器中。上面说的这些有可能不太好懂,下面简单的列举几个例子吧:
# 寄存器寻址
# 将R1地址处的数据读出,保存到R0中
LDR R0, [R1]
# 前变基寻址
# 将(R1+0x4)地址处的数据读出,保存到R0中
LDR R0, [R1, #0x4]
# 将(R1-0x4)地址处的数据读出,保存到R0中
LDR R0, [R1, #-0x4]
# 将(R1+R2)地址处的数据读出,保存到R0中
LDR R0, [R1, R2]
# 将((R1+(R2<<2))地址处的数据读出,保存到R0中
LDR R0, [R1, R2, LSL #2]
# 带写回的前变基寻址
# 将(R1+R2)地址处的数据读出,保存到R0中,然后更新R1=R1+R2
LDR R0, [R1, R2]!
# 将((R1+(R2<<2))地址处的数据读出,保存到R0中,然后更新R1=((R1+(R2<<2))
LDR R0, [R1, R2, LSL #2]!
# 带写回的后变基寻址
# 将R1地址处的数据读出,保存到R0中,然后更新R1=R1+0x4
LDR R0, [R1], #0x4
# 将R1地址处的数据读出,保存到R0中,然后更新R1=R1-0x4
LDR R0, [R1], #-0x4
# 将R1地址处的数据读出,保存到R0中,然后更新R1=R1+R2
LDR R0, [R1], R2
# 将R1地址处的数据读出,保存到R0中,然后更新R1=R1-R2
LDR R0, [R1], -R2
STR指令的操作和LDR指令类似,这里就不列举了。
LDR
相关的伪指令语法如下所示:
# 将数据 constant 加载到 Rt 寄存器中
LDR Rt, =constant
# 将 label 所代表的地址加载到 Rt 寄存器中
LDR Rt, =label
下面是LDR伪指令简单的使用:
# 将 0xaa 加载到 R0 寄存器
LDR R0, =0xaa
# 将 _start 所代表的地址加载到 R0 寄存器
LDR R0, =_start
LDM
指令用于加载指定地址上的数据保存到一个或者多个寄存器中。STM
指令用于将一个或者多个寄存器中的数据存储到指定地址上。LDM
和STM
指令主要用于现场保护和数据复制。
LDM
指令的语法如下所示:
LDM{addr_mode}{cond} Rn{!},reglist{^}
STM
指令的语法如下所示:
STM{addr_mode}{cond} Rn{!},reglist{^}
addr_mode
:地址模式,用于数据块传输的地址模式,如下所示:
IA:每次传送后地址加4。
IB:每次传送前地址加4。
DA:每次传送后地址减4。
DB:每次传送前地址减4。
也可以使用相应的面向堆栈的寻址模式,如下所示:
FD:满递减堆栈(每次传送前地址减4)。
ED:空递增堆栈(每次传送前地址加4)。
FA:满递增堆栈(每次传送后地址减4)。
EA:空递增堆栈(每次传送后地址加4)。
cond
:条件码。
Rn
:Rn存储了用于传输的初始地址。
!
:如果存在,表示最终的地址要写回Rn
。
reglist
:用{}
括起来的一个寄存器或者多个寄存器组成的列表。它可以是一个寄存器范围。如果{}
中的寄存器超过一个,那么寄存器或者寄存器范围之间通过逗号(,
)分隔。
^
:如果在除了USR模式和SYS模式下存在该符号,意味着将发生下述的两个动作:
LDM
指令时,如果寄存器列表中包含PC时,那么除了正常的多寄存器传送外,会将SPSR 拷贝到CPSR 中,这可用于异常处理返回。上面的内容可能不是很好理解,下面简单的列举写例子:
LDMIA例子如下所示:
# IA:每次传送后地址加4,下面是指令执行流程的分解:
# R0=[R1]
# R2=[R1+4]
# R3=[R1+8]
# R4=[R1+12]
LDMIA R1, {R0, R2-R4}
# IA:每次传送后地址加4,最终地址要写回R1,下面是指令执行流程的分解:
# R0=[R1], R1=R1+4
# R2=[R1], R1=R1+4
# R3=[R1], R1=R1+4
# R4=[R1], R1=R1+4
LDMIA R1!, {R0, R2-R4}
STMIA例子如下所示:
# IA:每次传送后地址加4,下面是指令执行流程的分解:
# [R1]=R0
# [R1+4]=R2
# [R1+8]=R3
# [R1+12]=R4
STMIA R1, {R0, R2-R4}
# IA:每次传送后地址加4,最终地址要写回R1,下面是指令执行流程的分解:
# [R1]=R0, R1=R1+4
# [R1]=R2, R1=R1+4
# [R1]=R3, R1=R1+4
# [R1]=R4, R1=R1+4
STMIA R1!, {R0, R2-R4}
LDMIB例子如下所示:
# IB:每次传送前地址加4,下面是指令执行流程的分解:
# R0=[R1+4]
# R2=[R1+8]
# R3=[R1+12]
# R4=[R1+16]
LDMIB R1, {R0, R2-R4}
# IB:每次传送前地址加4,最终地址要写回R1,下面是指令执行流程的分解:
# R1=R1+4, R0=[R1]
# R1=R1+4, R2=[R1]
# R1=R1+4, R3=[R1]
# R1=R1+4, R4=[R1]
LDMIB R1!, {R0, R2-R4}
STMIB例子如下所示:
# IB:每次传送前地址加4,下面是指令执行流程的分解:
# [R1+4]=R0
# [R1+8]=R2
# [R1+12]=R3
# [R1+16]=R4
STMIB R1, {R0, R2-R4}
# IB:每次传送前地址加4,最终地址要写回R1,下面是指令执行流程的分解:
# R1=R1+4, [R1]=R0
# R1=R1+4, [R1]=R2
# R1=R1+4, [R1]=R3
# R1=R1+4, [R1]=R4
STMIB R1!, {R0, R2-R4}
LDMDA例子如下所示:
# DA:每次传送后地址减4,下面是指令执行流程的分解:
# R4=[R1]
# R3=[R1-4]
# R2=[R1-8]
# R0=[R1-12]
LDMDA R1, {R0, R2-R4}
# DA:每次传送后地址减4,最终地址要写回R1,下面是指令执行流程的分解:
# R4=[R1], R1=R1-4
# R3=[R1], R1=R1-4
# R2=[R1], R1=R1-4
# R0=[R1], R1=R1-4
LDMDA R1!, {R0, R2-R4}
STMDA例子如下所示:
# DA:每次传送后地址减4,下面是指令执行流程的分解:
# [R1]=R4
# [R1-4]=R3
# [R1-8]=R2
# [R1-12]=R0
STMDA R1, {R0, R2-R4}
# DA:每次传送后地址减4,最终地址要写回R1,下面是指令执行流程的分解:
# [R1]=R4, R1=R1-4
# [R1]=R3, R1=R1-4
# [R1]=R2, R1=R1-4
# [R1]=R0, R1=R1-4
STMDA R1!, {R0, R2-R4}
LDMDB例子如下所示:
# DB:每次传送前地址减4,下面是指令执行流程的分解:
# R4=[R1-4]
# R3=[R1-8]
# R2=[R1-12]
# R0=[R1-16]
LDMDB R1, {R0, R2-R4}
# DB:每次传送前地址减4,最终地址要写回R1,下面是指令执行流程的分解:
# R1=R1-4, R4=[R1]
# R1=R1-4, R3=[R1]
# R1=R1-4, R2=[R1]
# R1=R1-4, R0=[R1]
LDMDB R1!, {R0, R2-R4}
STMDB例子如下所示:
# DB:每次传送前地址减4,下面是指令执行流程的分解:
# [R1-4]=R4
# [R1-8]=R3
# [R1-12]=R2
# [R1-16]=R0
STMDB R1, {R0, R2-R4}
# DB:每次传送前地址减4,最终地址要写回R1,下面是指令执行流程的分解:
# R1=R1-4, [R1]=R4
# R1=R1-4, [R1]=R3
# R1=R1-4, [R1]=R2
# R1=R1-4, [R1]=R0
STMDB R1!, {R0, R2-R4}
在数据块的传输中:STMDB
和LDMIA
对应使用,STMIA
和LDMDB
对应使用。
在堆栈操作中:STMFD
和LDMFD
对应使用,STMFA
和LDMFA
对应使用。
在子程序或者异常处理时,使用LDMFD
和STMFD
进行现场保护的例子如下:
# 将R0-R7和LR入栈
STMFD SP!, {R0-R7, LR}
# 功能代码
MOV R0, #0x00
MOV R1, #0x11
MOV R2, #0x22
# 将R0-R7和LR出栈
LDMFD SP!, {R0-R7, LR}
同样的可以使用STMDB
和LDMIA
指令进行现场保护,因此上述代码可以修改成下述形式:
# 将R0-R7和LR入栈
STMDB SP!, {R0-R7, LR}
# 功能代码
MOV R0, #0x00
MOV R1, #0x11
MOV R2, #0x22
# 将R0-R7和LR出栈
LDMIA SP!, {R0-R7, LR}
push
和pop
指令主要用于子程序或者异常的现场保护。push
指令用于将寄存器内容压入堆栈。pop
指令用于将堆栈中的内容恢复到寄存器中。
push
指令的语法如下所示:
PUSH{cond} reglist
pop
指令的语法如下所示:
POP{cond} reglist
cond
:条件码。
reglist
:用{}
括起来的一个寄存器或者多个寄存器组成的列表。它可以是一个寄存器范围。如果{}
中的寄存器超过一个,那么寄存器或者寄存器范围之间通过逗号(,
)分隔。
push
指令等价于STMDB
指令。pop
指令等价于LDMIA
指令。
使用push
指令和pop
指令保护现场的例子如下所示:
# 将R0-R7和LR入栈
push {R0-R7, LR}
# 功能代码
MOV R0, #0x00
MOV R1, #0x11
MOV R2, #0x22
# 将R0-R7和LR出栈
pop {R0-R7, LR}
MOV
指令主要用于将数据搬移到寄存器中。MOV
指令的语法如下所示:
MOV{S}{cond} Rn, Rm
MOV{cond} Rn, #imm
S
:可选的后缀,如果指令中添加了S
,那么指令的执行结果将会影响到CPSR寄存器的标志位域。
cond
:条件码。
Rn
:目标寄存器。
Rm
:源寄存器。
imm
:立即数。
MOV
指令的使用例子如下:
# 将 R1 寄存器中的内容搬移到 R0 寄存器
MOV R0, R1
# 将 0xaa 搬移到 R0 寄存器
MOV R0, #0xaa
可以通过CPS
(Change Processor State)指令来修改处理器模式。CPS
指令也可以用来使能或者禁止异常。
CPS
指令的语法如下所示:
CPS #mode
CPSIE iflags{, #mode}
CPSID iflags{, #mode}
mode
是处理器的模式编码,比如在从其他模式下切换到SYS模式,使用下述代码即可:
# 切换到SYS模式
CPS #0x1f
IE
使能中断或者终止。
ID
禁止中断或者终止。
iflags
由下面的一种或者几种组成:
a
:表示异步终止(asynchronous abort);i
:表示中断(IRQ);f
:表示快中断(FIQ);下述代码是CPS
指令的一些简单用法:
# 使能中断
CPSIE I
# 禁止中断
CPSID I
# 使能异步终止和快中断
CPSIE AF
# 禁止异步终止和快中断
CPSID AF
# 使能中断并切换到SYS模式
CPSIE I, #0x1f
MRS
和MSR
指令可用于读写程序状态寄存器CPSR,APSR和SPSR。
在ARM处理器中,只有MRS
指令可以从程序状态寄存器CPSR,APSR和SPSR中读出数据到通用寄存器中。MRS
指令操作程序状态寄存器的语法如下:
MRS{cond} Rd, psr
cond
为条件码。
Rd
为目标寄存器,Rd
不允许为R15。
psr
为程序状态寄存器CPSR,APSR或者SPSR。
MRS
指令的示例代码如下所示:
# 将CPSR寄存器的值读取到R0中
MRS R0, CPSR
# 将SPSR寄存器的值读取到R1中
MRS R1, SPSR
# 将APSR寄存器的值读取到R2中
MRS R2, APSR
MSR
指令可以用来写程序状态寄存器CPSR,APSR和SPSR的全部或者部分域。MSR
指令操作程序状态寄存器的语法如下:
MSR{cond} psr, #constant
MSR{cond} psr, Rm
MSR{cond} psr_fields, #constant
MSR{cond} psr_fields, Rm
cond
为条件码。
psr
为程序状态寄存器CPSR或者SPSR。
constant
是一个8位立即数。ARM文档对于constant
的介绍如下:
constant is an 8-bit pattern rotated by an even number of bits within a 32-bit word. (Not available in Thumb.)
Rm
是源寄存器。
fields
由下面的一个或者多个组合而成:
c
:xPSR[7:0],控制位域;x
:xPSR[15:8],扩展位域;s
:xPSR[23:16],状态位域;f
:xPSR[31:24],标志位域;MSR
指令的示例代码如下所示:
# 切换到SYS模式
MRS R0, CPSR
ORR R0, R0, #0x1f
MSR CPSR, R0
# 切换到SYS模式
MSR CPSR_c, #0xDF
只有在除用户模式外的其他模式下才能够修改状态寄存器。
ARMv7-A体系结构的处理器提供了MRC
和MCR
指令用于对协处理器进行读写操作。MRC
指令用于将协处理器中的寄存器数据读取到ARM通用寄存器中。MCR
指令用于将ARM通用寄存器中的数据写入到协处理器的寄存器中。
MRC
指令的语法如下所示:
MRC{cond} coproc, opc1, Rt, CRn, CRm{, opc2}
cond
为条件码。
coproc
为协处理器名称,CP0CP15协处理器分别对应名称p0p15。
opc1
为协处理器要执行的操作码,取指范围为0~7。
Rt
为ARM通用寄存器,用于存储读取到的协处理器寄存器数据。
CRn
为协处理器寄存器,对于CP15协处理器来说,CRn
取值范围为c0~c15。
CRm
为协处理器寄存器,对于CP15协处理器来说,通过CRm
和opc2
一起来确定CRn
对应的具体寄存器。
opc2
为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。
MRC
指令使用示例如下:
# 读取主ID寄存器 MIDR 的数据到 R0 中.
MRC p15, 0, R0, c0, c0, 0
MCR
指令的语法如下所示:
MCR{cond} coproc, opc1, Rt, CRn, CRm{, opc2}
cond
为条件码。
coproc
为协处理器名称,CP0CP15协处理器分别对应名称p0p15。
opc1
为协处理器要执行的操作码,取指范围为0~7。
Rt
为ARM通用寄存器,用于存储要写入到协处理器寄存器中的数据。
CRn
为协处理器寄存器,对于CP15协处理器来说,CRn
取值范围为c0~c15。
CRm
为协处理器寄存器,对于CP15协处理器来说,通过CRm
和opc2
一起来确定CRn
对应的具体寄存器。
opc2
为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。
MCR
指令使用示例如下:
# 将 R0 中的配置数据写入到 SCTLR
MCR p15, 0, R0, c1, c0, 0
欢迎关注博主的公众号(微信搜索公众号:嵌入式那些事),可以扫描下面的公众号二维码:
如果文中有什么问题欢迎指正,毕竟博主的水平有限。
如果这篇文章对你有帮助,记得点赞和关注博主就行了^_^。
排版更好的内容见我博客的地址:http://www.only2fire.com/archives/158.html
注:转载请注明出处,谢谢!^_^