首先了解栈(stack)和堆(heap)
栈(stack)空间,用于局部变量,函数调时现场保护和返回地址,函数的形参等。
堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数分配的变量空间是 在堆上
堆栈指针寄存器
Cortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。SP 有两 个,但在同一时刻只能有一个可以用。
主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的 应用程序代码来使用。
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。 另外以下两点要注意:
大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。
R13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。
堆栈的基本操作
大部分芯片在上电以后C语言环境还没准备好,C语言中的函数调用涉及到出栈入栈,出栈入栈就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问,SP 指
针指向栈顶。芯片一上电 SP 指针还没有初始化,所以 C 语言没法运行。
汇编语法
GNU 汇编语法适用于所有的架构,并不是ARM 独享的, GNU 汇编由一系列的语句组成,
每行一条语句,每条语句有三个可选部分,如下:
label:instruction @ comment
label即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到
指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识
符都会被识别为一个标号。
instruction即指令,也就是汇编指令或伪指令。
@符号,表示后面的是注释,就跟C语言里面的“/*”和“*/”一样,其实在GNU 汇编文
件中我们也可以使用“/*”和“*/”来注释。 comment就是注释内容。
比如如下代码:
add:
MOVS R0, #0X12 @设置R0=0X12
上面代码中“add:”就是标号,“MOVS R0,#0X12”就是指令,最后的“@设置 R0=0X12”就是
注释。
用户可以使用.section伪操作来定义一个段,汇编系统预定义了一些段名:
.text 表示代码段
.data 初始化的数据段。
.bss 未初始化的数据段
.rodata 只读数据段。
我们当然可以自己使用.section来定义一个段,每个段以段名开始,以下一段名或者文件结
尾结束,比如
.section .testsection @定义一个testsetcion段
汇编程序的默认入口标号是_start,不过我们也可以在链接脚本中使用 ENTRY 来指明其它
的入口点,下面的代码就是使用_start作为入口标号:
.global _start
_start:
ldr r0, =0x12 @r0=0x12
上面代码中.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局
,常见的伪操作有:
.byte 定义单字节数据,比如.byte 0x12。
.short 定义双字节数据,比如.short 0x1234。
.long 定义一个 4字节数据,比如.long 0x12345678。
.equ 赋值语句,格式为:.equ 变量名,表达式,比如.equ num, 0x12,表示num=
.align 数据字节对齐,比如:.align 4 表示 4字节对齐。
.end 表示源文件结束。
.global 定义一个全局符号,格式为:.global symbol,比如:.global _start。
GNU 汇编同样也支持函数,函数格式如下:
函数名:
函数体
返回语句
GNU 汇编函数返回语句不是必须的,如下代码就是用汇编写的 Cortex-A7 中断服务函数:
/* SVC 中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
常用汇编指令
.equ
类似#define , 常量符号
.equ NVA 0x10000
.text
声明接下来的代码都是放在text段(链接文件)
.align 4
变量的对齐宽度
.thumb
表示接下来都使用汇编为thumb指令
thumb指令集是arm指令集的一个子集
.syntax unified
统一汇编语法
Cortex-m3为了兼容thumb指令和thumb2指令,使这两种指令可以使用统一的格式,引入了一种叫做"统一汇编语言UAL"的语法机制。简单说来就是我们不用关心我们使用的是thumb指令还是thumb2指令,而是统一使用32位thumb2指令的语法格式书写
.type xxx, %function
声明xxx为一个函数
.type port_start, %function
port_start:
ldr r0, r1
cpsid i
屏蔽可配置的优先级中断, 等同primask = 1
cpsie i
开启可配置的优先级中断, 等同primask = 0
push
压栈
push {r4, r5}
pop
出栈
pop {r4, r5}
ldr
字数据加载指令
ldr r0, =50
ldrb
字节数据加载指令
ldrb r0,[r1] @将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零。
ldrb r0,[r1,#8] @将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零。
str
字数据存储指令
str r5, [r4]
将r5寄存器中的值存放到r4寄存器地址中,相当于C的 *r4 = r5
strb
字节数据存储指令
add
加法指令
add r1, r1, #1
等同于: r1 = r1 + 1
adds
等同于add指令, 区别会影响到标志寄存器xpsr
sub
减法指令
sub r1, r1, #1
等同于: r1 = r1 - 1
subs
等同于sub指令, 区别会影响到标志寄存器xpsr
mov
一般传送指令
mov r1, #0
等同于:r1 = 0
msr
用于将操作数的内容传送到程序状态寄存器的特定域中, 操作数可以为通用寄存器或立即数
msr primask, #1
msr primask, r1
传送r1值的内容到primask
mrs
用于将程序状态寄存器的内容传送到通用寄存器中
mrs r0, psp #Process_Stack_Pointer
将psp指针中的内容存放到r0
bx
指令跳转到指定的目标地址中, 目标地址处指令即可是ARM指令也可以是Thumb指令
bx lr
返回子程序
cbz
比较指令,如果为零就转移
orr
用于两个操作数上的逻辑或运算
orr lr, lr, #0x04
lr中的值与立即数0x04相或,结果存储到lr中
stm
类似与str但是它的对象为一组寄存器
stm r0, {r4-r11}
将r4-r11寄存器值保存到r0所指向的内存
ldm
类似与ldr用于加载内存中的数据,但是它的对象为一块内存
ldm r0, {r4-r11}
将r0所指向的内存数据存储到r4-r11