嵌入式知识-学习笔记(2):ARM汇编指令集与伪指令
嵌入式知识-学习笔记(2):ARM汇编指令集与伪指令
首先说明一下指令和伪指令的区别:
指令:是CPU机器指令的助记符,经过编译后会得到一串由1、0组成的机器码,可以由 CPU读取执行。(是一种内容)
伪指令:本质上不是指令,是编译环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。(是一种工具)
一.ARM汇编的特点
ARM采用RISC架构,CPU本身不能直接读取内存(需要借助内部寄存器对外部内容进行读取。当要改变外部内存中的数据时,首先需要将外部内存中的数暂时读到内部寄存器并进行改写,再重新放回到外部内存中)。
因此在这之中需要有指令进行内存的读取任务:
ldr(load register)指令:将内存内容加载到通用寄存器。
str(store register)指令:将寄存器内容存入内存空间。
ldr和str组合使用来实现ARM CPU和内存数据交换。
1.ARM的8种寻址方式
ARM汇编在进行寄存器操作时,大概有8种寻址方式:
寻址方式 | 汇编语句 | 内容解析 |
---|---|---|
寄存器寻址 | mov r1,r2 | 将r2中的内容运送到r1中(这里r1和r2表示ARM中的寄存器) |
立即寻址 | mov r0,#0xFF00 | 将#后的16进制的数直接放到r0中 |
寄存器移位寻址 | mov r0,r1,lsl #3 | 把r1左移3位(相当于乘8)后放到r0中 |
寄存器间接寻址 | ldr r1,[r2] | [r2]表示一个内存地址,把该内存地址中的值赋给r1 |
基址变址寻址 | ldr r1,[r2,#4] | [r2]的地址加上4的地址,把该地址的内容赋给r1 |
多寄存器寻址 | ldmia r1!, {r2-r7,r12} | r1作为一个起始地址,将后面地址r2-r7,r12中的内容依次放在r1作为起始地址的地址中 |
堆栈寻址 | stmfd sp!,{r2-r7,lr} | 与上述类似,只不过放在堆栈中 |
相对寻址 | beq flag: | :flag:标号用于标记标号后面那句指令的地址,常用来表示入口点,函数名就是一个标号,C语言中的goto就可以跳转到一个标号,在ARM汇编中用指令b flag:就可以跳转到flag:对应的标号处执行,和beq flag:是一样的,其原理是相对于PC程序位置寄存器做一个偏移。 |
2.指令后缀
B(byte):功能不变,操作长度变为8位
H(half word):功能不变,操作长度变为16位
S(signed):功能不变,操作数变为有符号(如ldr,ldrb,ldrh,ldrsb,ldrsh)
S(S标志):功能不变,影响CPSR标志位(如mov和movs)
3.条件执行后缀
1.条件后缀是否成立不是取决于本句代码,而是取决于之前代码运行后的结果。
2.条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。
举例:
mov r0, r1 @相当于C语言中的r0=r1;
moveq r0, r1 @如果eq后缀成立,则直接执行mov r0, r1; 如果eq不成立则本句代码直接作废,相当于没有,类似于C语言中if(eq){r0=r1;}
条件后缀和CPSR的NZCV位相关,例如,如果上一句代码执行的结果将Z置为1,下一句带有eq条件后缀的语句就会被执行
4.多级指令流水线
- 多级流水线用于增加处理器处理指令的速度,允许CPU同时异步的执行多条指令,而非顺序执行。
- 多级可以简单那理解为把一条指令分为多个步骤来异步执行,例如:
(1)CPU把一条指令分为[取址,解码,执行]3个步骤,则为3级指令流水线
(2)第一条指令进行取值操作
(3)第一条指令取值完毕,进入解码操作,第二条指令紧随其后就开始执行取值操作
(4)第一条指令解码完毕,进入执行操作,第二条指令紧接着进入解码操作,同时第三条指令进入取值操作
(5)第一条指令执行完毕,第二条指令进入执行操作,第三条指令进入解码操作,第四条指令进入取值操作,依次类推 - 可见,多级流水线可以提高同时执行指令的数量,从而加速指令执行
- 需要注意的是,PC指向的是正在取值的指令,而非正在执行的指令,之间的差值就是流水线级数和单字节长度的乘积,在中断返回到PC的时候需要注意这个问题
二.常用的ARM指令
1.数据处理指令
指令名称 | 汇编语句 | 内容解析 |
---|---|---|
数据传输指令 | mov mvn | (1) mov(move): mov r1,r0 @两个寄存器之间数据传递; (2) mov r1,#0xff @将立即数赋值给目标寄存器; (3) mvn和mov用法一样,区别是mov原封不动传递,而mvn是按位取反后传递。 |
算数指令 | add sub rsb adc sbc rsc | add(加法运算) sub(减法运算) rsb(反减运算) adc(带进位的加法运算) sbc(带进位的减法运算) rsc(带进位的反减指令) |
逻辑指令 | and orr eor bic | and(逻辑与) orr(逻辑或) eor(逻辑异或) bic(位清除指令) |
比较指令 | cmp cmn tst teq | cmp(等价于sub r2,r0,r1(r2=r0-r1)) cmn(等价于add r0,r1) tst(对特定位进行操作) teq(对两个数进行异或);比较指令不用加s后缀就可以影响spsr中的标志位 |
乘法指令 | mvl mla umull umlal | |
前导零计数 | clz | 统计一个数的二进制位前面有几个0 |
2.cpsr访问指令
由于cpsr寄存器比较特殊,需要专门的访问指令:
mrs: 用来读cpsr
msr: 用来写cpsr
cpsr和spsr的区别: cpsr是程序状态寄存器,整个soc中只有一个,而spsr有5个,分别在5中异常模式下,作用是当进入异常模式时,用来保存之前普通模式下的cpsr,以在返回普通模式时恢复原来的cpsr。
3.跳转指令
b: 直接跳转。
bl(branch and link): 跳转前把返回地址放入lr中,以便返回,以便用于函数调用。
bx: 跳转同时切换到ARM模式,一般用于异常处理的跳转。
4.访存指令
ldr: 加载指定内存地址的数据到寄存器,按照字节访问
str: 加载指定寄存器数据到内存地址中,按照字节访问
ldm: 和ldr功能一样,一次多字节多寄存器访问
stm: 和str功能一样,一次多字节多寄存器访问
swp: 内存和寄存器互换指令,一边读一边写,例如:
swp r1,r2,[r0]把r0地址中的数赋给r1,再把r2赋给r0地址中,相当于做了一个交换。
5.软中断指令
swi(software interrupt):在软件层模拟产生一个中断,这个中断会传送给CPU,常用于实现系统调用
三.ARM汇编中的立即数
合法立即数: 经过任意位数的移位后非零部分可以用8位表示的即为合法立即数。
非法立即数: 不是合法即为非法。
举例:
0xff 是一个合法立即数,因为非零部分的ff可以用11111111来表示
0x000001ff 是一个非法立即数,1ff不能用8位来表示
四.协处理器与指令
1.协处理器
什么是协处理器?SOC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
CPU中的寄存器都是以R开头的,协处理器中的寄存器都是以C开头的。
2.协处理器指令
mrc: 读取CP15中的寄存器
mcr: 向CP15中的寄存器写数据
指令用法:mcr{<”cond”>} p15,<”opcode_1”>,<”Rd”>,<”Crn”>,<”Crm”>,{<”opcode_2”>}
其中:opcode_1:对于CP15永远为0; Rd:ARM通用寄存器; Crn:CP15寄存器,取值范围c0~c15; Crm:CP15寄存器,一般为c0; opcode_2:省略或者为0。
3.ldm,stm和栈
ldr与str只能访问4个字节,当数据较大的时候,就会明显的降低效率,这时就需要使用到ldm和stm,ldm与stm是大量的从寄存器与内存交换数据的方式,常用于在内存和寄存器之间大量读取和写入数据。
ldm(load register multiple) :读指令
stm(store register multiple):写指令
后缀的使用:
- ia:increase after,后增加,表示每个操作的时候,先传输数据,后增加内存地址
- ib:increase before,先增加,表示在每个操作的时候,先增加内存地址,再进行数据传输
- da:decrease after:和ia一样,差别在于减少地址
- db:decrease before:和ib一样,差别在于减少地址
- fd:full decrease:满递减堆栈,查看栈的描述
- ed:empty decrease:空递减堆栈
- fa:满递增堆栈
- ea:空递增堆栈
操作栈时使用相同的后缀就不会出错
堆栈(栈)
- 空栈:栈指针指向空位,每次可以直接存入,然后栈指针(SP)递增或者递减1格,取的时候要递增或者递减1格才能取出
- 满栈:SP指向栈最后1格数据,存入的时候需要先移动1格才能存入,取的时候可以直接取出
- 增栈:SP向地址增加的方向移动
- 减栈:SP向地址减少的方向移动
4.汇编语言中的符号
ldmia r0,{r2 - r3}
ldmia r0!,{r2 - r3}
感叹号的作用是r0的值在ldm过程中发生的增加或减少最后写回到r0去,也就是说ldm时会改变r0的值。
ldmfd sp!,{r0 - r6, pc}
ldmfd sp!,{r0 - r6, pc}^
^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
五.ARM汇编伪指令
1.GNU汇编符号
[@,#,//,/~/]: 注释,和C语言的//是一样的
:: 冒号,在汇编中以冒号结尾的是标号,标号标记标号后面的指令的地址
.: 点号,代表当前指令的地址,例如:[b .]指令会进入死循环
#: 立即数前面要加#或$,代表一个立即数(不区分进制)
2.常用GNU伪指令
.global_start: 给_start外部链接属性
.section .text: 指定当前段为代码段
.ascii .byte .short .long .word .quad .float .string: 定义各种类型的数据
.align 4: 以16字节对齐
.balignl 16 0x3C: b表示填充,align表示对齐,l表示long,以4字节为单位填充,16表示以16字节对齐,0x3C是用来填充的原料
.equ: 宏定义
.end: 表示一个文件的结束
.include: 用于包含头文件
.arm / .code32: 声明以下的代码是arm指令
.thumb / .code16: 声明以下的代码是thumb指令
举例:
(1).ascii 等价于定义变量
(2)IRQ_STACK_START:
.word 0x0badc0de
等价于 unsigned int IRQ_STACK_START = 0x0badc0de;
3.重要的几个伪指令
ldr: 大范围的地址加载指令
adr: 小范围的地址加载指令
adrl: 中等范围的地址加载指令
nop: 空操作
ARM中有一个ldr指令,还有一个ldr伪指令,在一般情况下使用的是伪指令。
举例:
ldr指令: ldr r0, #0xff
伪指令: ldr r0, =0xff
adr和ldr的区别: ldr加载的地址在链接时确定,而adr加载的地址在运行时确定,所以我们可以通过adr和ldr加载的地址比较来判断当前程序是否在链接时指定的地址运行。
上一篇: 嵌入式ARM汇编详解(持续更新)