GNU汇编语法
GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分,如下:
标号
lable : instruction @comment
lable:是标号,表示地址位置,有些(insrtruction)前有标号、有些没有。
通过标号可以得到指令(insrtruction)的地址,注意lable后面的“:”,任何以“:”结尾的标识符都会识别为一个lable(标号)。
insrtruction: 指令,是汇编指令或者伪指令
@: 后面的表示注释
comment: 注释的内容
add:
MOVS R0,#0X12 @设置R0=0X12
注意!ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。
伪操作
可以使用.section伪操作来定义一个段
.text 表示代码段
.data 表示数据段
.bss 未初始化的数据段
.rodata 只读数据段
定义一个段,以段名开始,以下一段名结束或文件结尾结束
.section.tsetsection @定义一个testsection段
.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。
汇编函数
undefined_Handler:
ldr r0,=undefined_Handler
bx r0
SVC_Handler:
ldr r0,=SVC_Handler
bx r0
Prefabort_Handler:
ldr r0,=Prefabort_Handler
bx r0
undefined_Handler: 函数名
ldr r0,=undefined_Handler 函数体
bx r0 函数返回指令,返回语句不是必须的
常用的汇编指令
处理器内部数据传输指令
使用处理器做的最多事情就是在处理器内部来回的传递数据,常见的操作有:
①、将数据从一个寄存器传递到另外一个寄存器。
②、将数据从一个寄存器传递到特殊寄存器,如CPSR 和 SPSR寄存器。
③、将立即数传递到寄存器。
数据传输常用的指令有三个:MOV、MRS 和 MSR,这三个指令的用法如表 7.2.1.1 所示
指令 | 目的 | 源 | 描述 |
---|---|---|---|
MOV | R0 | R1 | 将R1里面的数据复制到R0 |
MRS | RO | CPSR | 将特殊寄存器CPSR里面的数据复制到R0中 |
MSR | CPSR | R1 | 将R1中的数据复制到特殊寄存器CPSR中 |
MOV
MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器中,或者将一个立即数传递到寄存器里面,使用示例如下:
MOV RO,R1 @将寄存器 R1 中的数据传递给 R0,即 R0=R1
MOV RO,#OX12 @将立即数0x12传递给寄存器R0
MRS
MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令!使用示例如下:
MRS R0, CPSR @将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR
MSR
MSR 指令和 MRS 刚好相反,MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用 MSR,使用示例如下:
MSR CPSR, R0 @将R0 中的数据复制到 CPSR 中,即 CPSR=R0
存储器访问指令
ARM内核不能够直接访问存储器,比如RAM中的数据.IMX6UL中的外设寄存器就是RAM类型的。利用汇编来配置IMX6UL寄存器的时候,需要借助存储器访问指令。
- 把要配置的值写入RX中。
- 借助存储器访问指令,把RX中的数据写入到IMX6UL寄存器中。
指令 | 描述 |
---|---|
LDR Rd,[Rn,#offset] | 从存储器(RAM)Rn+offset的位置读取数据存放到ARM寄存器Rd |
STR Rd,[Rn,#offset] | 将ARM寄存器Rd的数据写入到存储器(RAM)的Rn+offset的位置 |
LDR
LDR 加载立即数使用的是“=”,而不是"”#“。最常用的就是读取CPU的寄存器的值。
IMX6UL寄存器GPIO1_GDIR 的地址是 0X0209C004
ldr r0,=0x0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
ldr r1,[r0] @读取地址 0X0209C004 中的数据到 R1 寄存器中
STR
STR就是将数据写入到存储器(RAM)中,
IMX6UL寄存器GPIO1_GDIR 的地址是 0X0209C004,现在将这个值配置为0x20000002
ldr r0,=0x0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
ldr r1,=0x20000002 @数值 0x20000002 加载到 R1 中,即 R1=0x20000002
str r1,[r0] @写数据,将R1中的数据写入到R0所保存的地址中去
LDR 和 STR 都是按照字进行读取和写入的,也就是操作的 32 位数据,如果要按照节、半字进行操作的话可以在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB和STRB,按半字操作的指令就是 LDRH 和 STRH。
压栈和出栈指令
当我们在A函数中调用B函数的时候,要想回来的时候,A函数的代码能正常的执行,要在跳转B函数之前,把当前的处理器的状态进行保存(保存R0-R15)这些寄存器。
在进行保存的时候,需要进行压栈的操作,PUSH。
在回复现在的时候,需要进行出栈的操作,POP。
指令 | 描述 |
---|---|
PUSH {reg list} | 将寄存器列表存入到栈中 |
POP{reg list} | 从栈中恢复寄存器列表 |
将R0~R3和R12这5个寄存器压栈,当前的SP栈指针指向0x80000000,处理器的栈是向下增长的。
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
这个时候SP指针指向了0x7FFFFFFC,这个时候如果在将LR进行压栈。
PUSH {LR} @将 LR 进行压栈
将LR压栈以后的图形
出栈的时候,就是冲栈顶当前SP的位置,开始后入的先出。
POP {LR} @先恢复 LR
POP {R0~R3,R12} @在恢复 R0~R3,R12
还有一种写法“STMFD SP!”和“LDMFD SP!”
STMFD SP!,{R0-R3,R12} @R0~R3,R12 入栈
STMFD SP!,{LR} @LR 入栈
LMDFD SP!,{LR} @LR 先恢复LR
LMDFD SP!,{R0-R3,R12} @LR 在恢复R0~R3,R12
跳转指令
有多种跳转操作,比如:
①、直接使用跳转指令 B、BL、BX 等。
②、直接向 PC 寄存器里面写入数据。
指令 | 描述 |
---|---|
B{label} | 跳转到lable |
BX{RM} | 间距跳转,跳动存放于Rm中的地址处,并切换指令集 |
BL{label} | 跳转到标号地址,并将返回地址保存在LR中 |
BLX{RM} | 结合BX和BL的特点,跳转到RM指定地址并将返回地址保存在LR中,切换指令集。 |
B指令
这是最简单的跳转指令,B 指令会将 PC(R15)寄存器 的值设置为跳转目标, 一旦执行 B 指令,ARM 处理器就会立即跳转到指定的目标地址。如果要调用的函数不会再返回到原来执行处,那就可以用 B 指令,如下示例:
_START:
LDR SP ,=0X80200000 @设置堆栈指针
B main @跳转到main函数
跳转后,会在回来的时候,就要B
BL指令
在跳转前,将在寄存器LR(R14)中保存当前的PC(R15)寄存器的值,因此可以后面,可以通过吧LR中的值,重新加载到PC中,继续跳转前的代码运行。
push {r0,r1}
cps #0x13 @进入到svc模式,
bl system_irqhandler @加载C语言的中断服务函数
cps #0x12 @进入到IRQ模式