您当前的位置: 首页 >  ar

风间琉璃•

暂无认证

  • 1浏览

    0关注

    337博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

ARM汇编基础

风间琉璃• 发布时间:2021-11-20 22:58:56 ,浏览量:1

文章目录
  • 前言
  • 一、GNU 汇编语法
  • 二、内核寄存器组
    • 1.通用寄存器
    • 2.程序状态寄存器
  • 三、ARM指令
    • 1.数据传输指令
    • 2.访问指令
    • 3.压栈(PUSH)和出栈(POP)指令
    • 4.跳转指令
    • 5.算术运算指令
    • 6.逻辑运算指令
  • 总结

提示:以下是本篇文章正文内容

一、GNU 汇编语法 ARM汇编指令,编译是使用GCC交叉编译器,所以ARM汇编指令要符合GUN汇编语法

语法格式:

label: instruction @ comment

(1)label :标号,表示地址位置,可以通过标号得到这条指令的地址,标号也可以用来表示数据地址,可以通过跳转指令执行到该指令 注意 :label 后面的“:”,任何以“:”结尾的标识符都会被识别为一个标号

(2)instruction :指令,汇编指令或伪指令

(3)@符号 : 注释符,也可以用C语言中的/* */注释,x86中用分号,inter中用感叹号,好像可以混用(我没试过,反正X86和inter可以顺便混用)

(4)comment:注释内容

eg:

add:
	MOVS R0, #0X12  @设置 R0=0X12

注意赋值的方向,x86和gnu是从右向左,而inter是从左到右

使用.section 伪操作来定义一个段

.text 表示代码段 .data 初始化的数据段。 .bss 未初始化的数据段。 .rodata 只读数据段

汇编程序的默认入口标号是_start

.global _start
_start:   @ 使用_start 作为入口标号
	ldr r0, =0x12 

.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量

常见的伪操作

(1).byte 定义单字节数据, .byte 0x12 (2).short 定义双字节数据, .short 0x1234 (3).long 定义一个 4 字节数据, .long 0x12345678 (4).equ 赋值语句,格式为: .equ 变量名,表达式, .equ num, 0x12,表示 num=0x12 (5).align 数据字节对齐, .align 4 表示 4 字节对齐 (6).end 表示源文件结束 (7).global 定义一个全局符号,格式为: .global symbol, .global _start

GNU 汇编函数 函数格式:

函数名:
	函数体
	返回语句

SVC 中断

SVC_Handler:
	ldr r0, =SVC_Handler
	bx r0    @返回语句

SVC_Handler是函数名,中间那句是函数体,bx指令是返回指令,所以最后一句是返回语句

二、内核寄存器组

由于后面ARM指令要对寄存器进行操作这里简单介绍一下Cortex-A 的内核寄存器组,注意不是芯片的外设寄存器

在这里插入图片描述 Cortex-A 寄存器主要包括 15个通用寄存器(R0~R14)、一个程序状态寄存器和一个程序计数器 PC

1.通用寄存器

R0~R15 就是通用寄存器

(1)未备份寄存器(R0~R7) 在所有的处理器模式下这 8 个寄存器都是同一个物理寄存器, 没有作为特殊用途

(2)备份寄存器( R8~R14) 备份寄存器中的 (R8-R12)5个寄存器有两种物理寄存器,在快速中断模式下(FIQ)它们对应着 Rx_irq(x=8-12)物理寄存器,其他模式下对应着 Rx(8~12)物理寄存器, 因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程

R13 一共有 8 个物理寄存器, 一个是用户模式(User)和系统模式(Sys)共用 的,剩下的 7 个分别对应 7 种不同的模式R,13 也叫做 SP,用来做为栈指针,应用程序会初始化 R13,使其指向该模式专用的栈地址

R14 一共有 7 个物理寄存器,其中一个是用户模式(User)、系统模式(Sys)和超级监视模式(Hyp)所共有的,剩下的 6 个分别对应 6 种不同的模式,

LR 寄存器在 ARM的作用 1.R14(LR)来存放当前子程序的返回地址 如果使用 BL 或者 BLX来调用子函数的话, R14(LR)被设置成该子函数的返回地址,在子函数中,将 R14(LR)中的值赋给 R15(PC)即可完成子函数返回

1.MOV PC, LR @寄存器 LR 中的值赋值给 PC,实现跳转

2.@在子函数的入口出将 LR 入栈
PUSH {LR} @将 LR 寄存器压栈

3.@子函数出栈
POP {PC}  @将上面压栈的 LR 寄存器数据出栈给 PC 寄存器

2.当异常发生以后,该异常模式对应的 R14 寄存器被设置成该异常模式将要返回的地址,R14 也可以当作普通寄存器使用

(3)程序计数器 PC(R15) 程序计数器 R15(PC), ==保存着当前执行的指令地址值加 8 个字节 ==

ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执,比如当前正在执行第一条指令的同时也对第二条指令进行译码,第条指令也同时被取出存放在 R15(PC)中

以第一条指令为参考点,那么 R15(PC)中存放的就是第三条指令,也就是 R15(PC)总是指向当前正在执行的指令地址再加上 2 条指令的地址,对于 32 位的 ARM 处理器,每条指令是 4 个字节,所以 R15 (PC)值 = 当前执行的程序位置 + 8 个字节

2.程序状态寄存器 三、ARM指令 1.数据传输指令

使用处理器做的最多事情就是在处理器内部来回的传递数据,比如将数据从一个寄存器传递到另外一个寄存器;将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器;将立即数传递到寄存器

(1)MOV指令 功能:用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面

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

注意赋值的方向是从右到左!!!

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

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

(3)MSR 指令 功能:将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用 MSR,刚好与MRS相反

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

记忆:看赋值方向从右向左,S代表特殊寄存器,MRS中S在最右边表示将S特殊寄存器的数据传给R普通寄存器

在这里插入图片描述

2.访问指令

ARM不能直接访问存储器,阿尔法中的寄存器就是RAM类型的,所以,需要ARM指令来配置阿尔法的寄存器。一般先把要写入的数据存到RX(0-12)通用寄存器中,然后借助存储器访问指令将rx中的数据,写到阿尔法寄存器中,读数据也是类似的

(1)LDR指令 作用:从存储加载数据到寄存器Rx 中, LDR也可以将一个立即数加载到寄存器Rx中,LDR加载立即数的时候要使用“=”,而不是“#”

在嵌入式开发中,LDR最常用的就是读取CPU的寄存器值,比如读取GPIO1_GDIR寄存器中的数据,其地址为0X0209C004,

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

(2)STR指令 作用:将数据写到存储器中

配置寄存器 GPIO1_GDIR 的值为 0X20000002

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

LDR 和 STR 都是按照字进行读取和写入的,也就是操作的 32 位数据

如果要按照字节、半字进行操作的话可以在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB 和STRB,按半字操作的指令就是 LDRH 和 STRH

注:这里STR指令操作方向就是从左到右了!!!前面几条都是从右往左

3.压栈(PUSH)和出栈(POP)指令

在C语言中,一个函数调用另外一个函数,在跳转到另外一个函数之前,要将当前处理器状态保存起来(R0-R15寄存器中的值),该函数执行完后,再用前面保存的寄存器值恢复R0~R15,回到调用函数里面。

保存 R0-R15 寄存器的操作就叫做现场保护,恢复 R0-R15 寄存器的操作就叫做恢复现场,在进行现场保护的时候需要进行压栈(入栈)操作,恢复现场就要进行出栈操作

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

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

eg: 若当前的 SP 指针指向 0X80000000,将 R0~R3 和 R12 这 5 个寄存器压栈,压栈指令:

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

压栈后如图: 在这里插入图片描述 注:处理器的堆栈是向下增长的 此时,栈顶指针SP指向了0X7FFFFFEC

继续将LR压栈,指令

PUSH {LR} @将 LR 进行压栈 在这里插入图片描述

依次将各个寄存器出栈,出栈指令

POP {LR} POP {R0~R3,R12}

出栈的就是从栈顶(SP指向的地址),地址依次减小来提取堆栈中的数据 到要恢复的寄存器列表中

补充:出栈,入栈的另一种写法

PUSH —> STMFD SP! POP --> LDMFD SP! R0~R3,R12 入栈:STMFD SP!, {R0~R3, R12}

STMFD 可以分为两部分: STM 和 FD,LDMFD 也可以分为 LDM 和FD, LDR 和 STR这两个是数据加载和存储指令,但是每次只能读写存储器中的一个数据,而STM 和 LDM 就是多存储和多加载,可以连续的读写存储器中的多个连续数据

FD 是 Full Descending 的缩写,即满递减的意思,ARM 使用的 FD 类型 的堆栈,== SP 指向最后一个入栈的数值==,堆栈是由高地址向下增长的,因此最常用的指令就是 STMFD 和 LDMFD,STM 和 LDM 的指令寄存器列表中编号小的对应低地址,编号高的对应高地址

4.跳转指令

(1)B指令 B 指令会将PC寄存器的值设置为跳转目标地址, 一旦执行 B 指令,ARM 处理器就会立即跳转到指定的目标地址

如果要调用的函数不会再返回到原来的执行处,可以用 B 指令

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

这就是在汇编中初始化C运行的环境,然后跳转到C语言的main函数里面

(2)BL指令 在跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行

push {r0, r1} @保存 r0,r1
cps #0x13 @进入 SVC 模式,允许其他中断再次进去

bl system_irqhandler @加载 C 语言中断处理函数到 r2 寄存器中

cps #0x12 @进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0X10] @中断执行完成,写 EOIR

Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等,但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到irq 汇编中断服务函数,进行恢复现场

bl指令后,执行 C 语言版的中断处理函数,当处理完成以后是需要返回来继续执行下面的程序 在这里插入图片描述

5.算术运算指令

在这里插入图片描述 注:进位和借位我记得在X86中有个专门的寄存器中标记位来记录的

6.逻辑运算指令

在这里插入图片描述

关注
打赏
1665385461
查看更多评论
立即登录/注册

微信扫码登录

0.0434s