适用于Cortex-A的常见GNU汇编语法

语句

GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分:label: instruction @ comment

  • label:标号,表示地址位置
  • instruction:指令,汇编指令或伪指令
  • @ comment:注释

ARM中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。

伪操作(伪指令)

用户可通过.section伪操作来定义一个段,汇编系统预定义了一些段名,包括:

  • .text:代码段
  • .data:初始化的数据段
  • .bss:未初始化的数据段
  • .rodata:只读数据段

.section也可用于自定义段,每个段以段名开始,以下一段名或文件结尾结束。如.section .testsection @顶一个testsection段

汇编程序的默认入口标号是_start,不过我们也可以在链接脚本中使用ENTRY来指明其它的入口点,下面的代码就是使用_start作为入口标号:
``
.global _start

_start:
ldr r0, =0x12 @r0=0x12
``
代码中.global是伪操作,表示_start是一个全局标号(性质类似于C语言中的全局变量)。

常见的伪指令有:

  • .global:声明全局符号,如.global main @声明main为全局符号
  • .section:定义段
  • .word:定义字数据
  • .asciz:定义以null结尾的字符串,如.asciz "Hello World"
  • .byte:定义单字节数据,比如.byte 0x12
  • .short:定义双字节数据,比如.short 0x1234
  • .long:定义一个4字节数据,比如.long 0x12345678
  • .equ:赋值,如.equ num, 0x12表示num=0x12
  • .align:数据字节对齐,.align 4表示4字节对齐
  • .end:表示源文件结束

处理器内部数据传输指令

最常见的一类指令,包括:

  • 将数据从一个寄存器传递到另外一个寄存器
  • 将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器
  • 将立即数传递到寄存器

MOV

将数据从一个寄存器拷贝到另一个寄存器,或将一个立即数传递到寄存器中。

1
2
MOV R0, R1      @寄存器R1中的数据传递给R0
MOV R0, #0x12 @立即数0x12传递给R0

MRS

将特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器。注意:读取特殊寄存器数据时只能使用MRS指令。

1
MRS R0, CPSR

MSR

将普通寄存器的数据传递至特殊寄存器,即写入特殊寄存器。

1
MSR CPSR, R0

存储器访问指令

ARM无法直接访问存储器(如RAM)。以I.MX6UL为例,其寄存器为RAM类型,汇编配置寄存器时需借助存储器访问指令,将待配置的值写入到Rx(x=0~12)寄存器中,然后借助存储器访问指令将Rx中的数据写入之MX6UL的寄存器中,读取亦同。

LDR

用于从存储器加载数据至寄存器Rx中,也可以将一个立即数加载到寄存器Rx中。加载立即数时,使用=而不是#,如=0xFFFFFFFF

在嵌入式开发中,LDR最常用的就是读取CPU的寄存器值:

1
2
LDR R0, =0x0209C004     @将寄存器地址0x0209C004加载到R0中,准备寻址
LDR R1, [R0] @读取地址0x0209C004中的值到R1

STR

与LDR相反,STR用于将数据写入到存储器中。

1
2
3
LDR R0, =0x0209C004     @寄存器地址0x0209C004写入R0
LDR R1, =0x20000002 @待写入值0x20000002写入R1
STR R1, [R0] @R1中的值写入R0中所保存的地址中

跳转指令

B指令

B指令将PC寄存器的值设置为目标跳转地址,一旦执行B指令,ARM处理器就会立即跳转至指定的目标地址。如果待调用函数不会再返回到原来的执行处,就可以使用B指令。

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

BL指令

相比B指令,BL指令在跳转之前会在寄存器 LR(R14) 中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用一个基本但常用的手段,常见于各种中断服务程序。

Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到 irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用 B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令,示例代码如下:

1
2
3
4
5
6
7
8
push {r0, r1}           @入栈,保存R0, R1
cps #0x13 @进入SVC模式,CPS指令用于直接修改CPSR寄存器的M[4:0],让CPU进入不同的模式

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

cps #0x12 @进入IRQ模式
pop {r0, r1} @恢复现场
str r0, {r1, 0#10} @中断执行完成,写EOIR

ARM中常见的寄存器

几个ARM中常用的寄存器:

  • IP寄存器:内部程序调用暂存寄存器,子程序的连接text段中常使用该规则
  • SP寄存器:栈指针寄存器,用于存储当前栈顶地址。程序执行过程中,栈是用来存储临时变量、函数调用返回地址等数据的重要数据结构,SP寄存器的值会随着栈的变化而变化
  • LR寄存器:连接寄存器,程序跳转(子程序调用,中断跳转)后,arm自动在该寄存器中存入原程序(未跳转)的下一条指令的地址,也叫函数调用返回地址。当一个函数被调用时,LR寄存器会存储调用该函数的下一条指令的地址,当函数执行完毕后,程序会跳转到LR寄存器中存储的地址继续执行。
  • PC寄存器:程序计数器,保存的是当前正在取指的指令的地址(arm采用2级流水线,因此是当前正在执行指令的地址+8)。PC寄存器是ARM中的程序计数器,用于存储下一条将要执行的指令的地址。

此外,还有两个比较重要的状态寄存器:

  • CPSR:程序状态寄存器,在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。CPSR在用户级编程时用于存储条件码。
  • SPSR:程序状态保存寄存器,每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。

二者常用于MRS或MSR指令,用于spsr中的值转移到寄存器或把寄存器的内容加载到spsr中,如:

1
2
mrs r0, spsr                /* 读取spsr寄存器 */
msr spsr_cxsf, r0 /* 恢复spsr */

ARM进入异常模式后,SPSR自动保存进入异常前的CPSR的值,以便异常返回后恢复异常发生时的工作状态。

LED点灯示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
.global _start  /* 全局标号 */

/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 例程代码 */
/* 1、使能所有时钟 */
ldr r0, =0X020C4068 /* CCGR0 */
ldr r1, =0XFFFFFFFF
str r1, [r0]

ldr r0, =0X020C406C /* CCGR1 */
str r1, [r0]

ldr r0, =0X020C4070 /* CCGR2 */
str r1, [r0]

ldr r0, =0X020C4074 /* CCGR3 */
str r1, [r0]

ldr r0, =0X020C4078 /* CCGR4 */
str r1, [r0]

ldr r0, =0X020C407C /* CCGR5 */
str r1, [r0]

ldr r0, =0X020C4080 /* CCGR6 */
str r1, [r0]


/* 2、设置GPIO1_IO03复用为GPIO1_IO03 */
ldr r0, =0X020E0068 /* 将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 */
ldr r1, =0X5 /* 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 */
str r1,[r0]

/* 3、配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
ldr r0, =0X020E02F4 /*寄存器SW_PAD_GPIO1_IO03_BASE */
ldr r1, =0X10B0
str r1,[r0]

/* 4、设置GPIO1_IO03为输出 */
ldr r0, =0X0209C004 /*寄存器GPIO1_GDIR */
ldr r1, =0X0000008
str r1,[r0]

/* 5、打开LED0
* 设置GPIO1_IO03输出低电平
*/
ldr r0, =0X0209C000 /*寄存器GPIO1_DR */
ldr r1, =0
str r1,[r0]

/*
* 描述: loop死循环
*/
loop:
b loop

适用于Cortex-A的常见GNU汇编语法
http://akichen891.github.io/2025/02/18/适用于Cortex-A的常见GNU汇编语法/
作者
Aki
发布于
2025年2月18日
更新于
2025年3月4日
许可协议