ARM汇编基础-Cortex-A7常用汇编指令

2334 字
12 分钟
ARM汇编基础-Cortex-A7常用汇编指令

一、 处理器内部数据传输指令#

常用的内部数据传输指令有三个:MOVEMRSMSR

指令目的描述
MOVER0~R14 通用寄存器(R15也可以)R0~R14 通用寄存器(R15也可以)、立即数将源寄存器数据移动到目的寄存器
MRSR0~R14 通用寄存器CPSR、SPSR将特殊寄存器 CPSR 里面的数据复制到 通用寄存器(R0~R14)
MSRCPSRR0~R14 通用寄存器通用寄存器(R0~R14) 中的数据移动到特殊寄存器 CPSR

1.1、MOV指令指令#

MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面,使用示例如下:

MOV R0,R1 ;将寄存器 R1 中的数据传递给 R0,即 R0=R1
MOV R0, #0X12 ;将立即数 0X12 传递给 R0 寄存器,即 R0=0X12

1.2、MRS指令#

MRS 指令用于将特殊寄存器(如 CPSRSPSR )中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令

MRS R0, CPSR ;将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR

1.3、MSR指令#

MSR 指令用于将通用寄存器(==不能是立即数==)中的值传递给特殊寄存器(如 CPSRSPSR ),特殊寄存器只能使用 MSR0

MSR CPSR, R0 ;将 R0 中的数据复制到 CPSR 中,即 CPSR=R0

二、存储器访问指令#

ARM不能够直接访问存储器(如RAM),必须通过存储器访问指令 LDRSTR 进行访问。

2.1、LDR指令#

此命令常用于将存储器的值,保存到通用寄存器,命令格式如下:

LDR Rd, [Rn , #offset]
  • Rd

    通用寄存器(R0~R12),(R13~R15也行,但危险),用来保存冲存储器得到的数据。

  • [Rn , #offset]

    存储器地址,从通用寄存器 (R0~R12),(R13~R15也行,但危险) 读取的值 + offset 作为存储器地址,表示要读取的目标存储器地址。

当然,LDR也可以用于加载立即数,即实现MOV指令一样的效果,但是这么使用时,其格式如下:

LDR Rd, =立即数 ;使用LDR将一个立即数的值加载到通用寄存器中

用例:

LDR R0, =0X0209C004 ;将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
LDR R1, [R0] ;读取地址 0X0209C004 中的数据到 R1 寄存器中

命令按字进行数据传输,如果需要按照半字、字节操作,可以在其末尾加上H、B,如:

LDRH Rd, [Rn , #offset] ;按半字传输
LDRB Rd, [Rn , #offset] ;按字节传输

2.2、STR指令#

此命令常用于将数据写入到存储器中,命令格式如下:

STR Rd, [Rn , #offset]

使用方法和 LDR 类似,但是不能是一个立即数,只是换了一个传输方向。

  • Rd:

    用于保存要写入的数据的通用寄存器

  • [Rn , #offset] :

    要写入的存储器地址:Rn + offset

用例:

LDR R0, =0X0209C004 ;将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
LDR R1, =0X20000002 ;R1 保存要写入到寄存器的值,即 R1=0X20000002
STR R1, [R0] ;将 R1 中的值写入到 R0 中所保存的地址中

命令按字进行数据传输,如果需要按照半字、字节操作,可以在其末尾加上H、B,如:

STRH Rd, [Rn , #offset] ;按半字传输
STRB Rd, [Rn , #offset] ;按字节传输

三、压栈和出栈指令#

3.1、压栈和出栈#

压栈和出栈即函数调用时的出入栈(stack)操作。调用函数后,用户保护和恢复现场。

压栈的指令为 PUSH,出栈的指令为 POP, PUSH 和 POP 是一种多存储和多加载指令,即可以一次操作多个寄存器数据,他们利用当前的栈指针 SP 来生成地址。

这两条的指令格式如下:

指令描述
PUSH <reg list>将寄存器列表存入栈中
POP <reg list>从栈中恢复寄存器列表

3.2、PUSH指令#

指令将通用寄存器 (R0~R15) 中的内容压入栈内存,使用方法如下:

PUSH {R0~R3, R12} ;将 R0~R3 和 R12 压栈

假设当前的栈内存的栈顶地址为0x80000000,当执行压栈操作后,其栈结构如图:

栈顶地址变为了 0x80000000 - ( 4 + 1 ) * 4 = 0x7FFFFFEC ,即此时SP指向了 0x7FFFFFEC

需要注意的是压栈顺序,是按照寄存器标号,由大到小依次入栈的,即在此用例中,R12先入栈,然后才依次是R3R2R1R0

因此最终栈顶SP只想的地址是R0的内容。

再次基础上,在对LR寄存器压栈:

PUSH {LR} ;将 LR 进行压栈

存储结构又变为:


3.3、POP指令#

从栈中恢复寄存器列表,按照3.2的模型,当需要从栈内存恢复现场时:

POP {LR} ;先恢复 LR
POP {R0~R3,R12} ;在恢复 R0~R3,R12

POP指令按照入栈顺序,一次将栈内容推出。

3.4、STMFD、LDMFD指令#

出入栈除了可以使用PUSHPOP指令外,还可以使用STMFDLDMFD进行替代。

STMFDLDMFD分为两个部分,STM + FDLDM + FDSTM、LDM和前面提到的STR、LDR指令类似,都是对存储器进行操作,但是STR、LDR是操作单个寄存器,而STM、LDM是操作多个寄存器,而其中的FD代表着Full Descending,即满递减的意思。

STMFD存储前,指针会先减少要存储的内容大小,为数据腾出空间,然后再写入数据。

LDMFD加载前,指针会先增加要加载的内容大小,为数据腾出空间,然后再加载数据。

对于堆栈而言,刚好地址是向下增长的。因此可以使用STMFD代替PUSH,使用LDMFD代替POP

入栈:

STMFD SP!,{R0~R3, R12} ;将SP中的存储地址减少腾出空间后,写入R0~R3 和 R12的值,完成后更新SP,即R0~R3 和 R12压栈
STMFD SP!,{LR} ;将SP中的存储地址减少腾出空间后,写入LR的值,完成后更新SP,即将LR压栈

出栈:

LDMFD SP!,{LR} ;将SP中的存储地址增加后,传输到LR寄存器中,完成后更新SP,即LR出栈
LDMFD SP!,{R0~R3,R12} ;将SP中的存储地址增加后,传输到R0~R3 和 R12寄存器中,完成后更新SP,即R0~R3 和 R12出栈

里面SP后面跟的!表示更新SP本身的值,即传输完成后,将更新SP中的值,指向新的栈顶地址,它有通过此命令传输的数据大小决定。

当然,STM + FDLDM + FD 代表了STMFDLDMFD,那也就存在着其他组合方式,如:

  • IA (Increment After)
  • IB (Increment Before)
  • DA (Decrement After)
  • DB (Decrement Before)
  • FD (Full Descending)
  • FA (Full Ascending)
  • ED (Empty Descending)
  • EA (Empty Ascending)

这里就不过多做介绍了。


四、跳转指令#

有多种跳转操作,比如:

  • 直接使用跳转指令 B、 BL、 BX 等。
  • 直接向 PC 寄存器里面写入数据

但常用的还是使用跳转指令B、BL、BX。

指令描述
B <label>跳转到 label,如果跳转范围超过了+/-2KB,可以指定 B.W <label>使用 32 位版本的跳转指令,
这样可以得到较大范围的跳转
BX <Rm>间接跳转,跳转到存放于 Rm 中的地址处,并且切换指令集
BL <label>跳转到标号地址,并将返回地址保存在 LR
BLX <Rm>结合 BX 和 BL 的特点,跳转到 Rm 指定的地址,并将返回地 址保存在 LR 中,切换指令集

用的最多的还是 BBL 指令

4.1、B指令#

这是最简单的跳转指令, B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指令, ARM 处理器就会立即跳转到指定的目标地址。==如果要调用的函数不会再返回到原来的执行处==,那就可以用 B 指令,如下示例:

_start:
ldr sp,=0X80200000 ;设置栈指针
b main ;跳转到 main 函数

这段代码就是典型的跳转到C代码的汇编指令,但这里只初始化了SP指针,还有其他的初始化DDR等操作没有做。

代码中初始化了sp指针,然受使用b跳转到main函数。

4.2、BL#

相对于B指令,BL跳转前,会将当前PC寄存器的值保存在LR寄存器,即保存返回地址。因此使用BL跳转后的函数,可以返回。

比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。

例如:

push {r0, r1} ;保存 r0,r1
cps #0x13 ;进入 SVC 模式,允许其他中断再次进去
bl system_irqhandler ;加载 C 语言中断处理函数到 r2 寄存器中
cps #0x12 ;进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0X10] ;中断执行完成,写 EOIR

上述代码中 bl system_irqhandle 就是执行 C 语言版的中断处理函数,当处理完成以后是需要返回来继续执行下面的程序,所以使用了 BL 指令。


五、算数运算指令#

算数运算指令有很多。

常用的运算指令如下图:

在嵌入式开发中最常会用的就是加减指令,乘除基本用不到。


六、逻辑运算指令#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

评论区

Profile Image of the Author
lim
Hello, I'm lim.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
站点统计
文章
11
分类
4
标签
5
总字数
14,426
运行时长
0
最后活动
0 天前