ARM指令也称ARM汇编指令集,是用来操作及控制ARM处理器及其相关设备的32bit的汇编指令,相对于16bit的thumb指令集而言功能更加强大,包含指令与伪指令。现将常用指令归纳汇总如下:
一、指令、伪指令
指令:是机器码的助记符,经过汇编器编译为机器码后,可以由CPU执行。
伪指令:用来指导汇编器编译指令,是汇编器的产物,终不会生成机器码。
二、ARM指令的编写风格
汇编代码大写:在Windows中的IDE开发环境中一般都大写。
汇编代码小写:在Linux环境中,好遵循GNU风格,即指令一般用小写。
三、ARM汇编代码文件后缀的大小写问题
Windows环境:因为Windows不区分大小写,所以汇编文件后缀大写、小写编译过程没有区别,即test.S与test.s编译结果一致。
Linux环境:Linux环境是严格区分大小写的,test.S与test.s会被当成不同的文件来处理(处理过程也不一致)。后缀小写的test.s文件,在编译阶段不进行预处理操作,所以不能在这里面写预处理的语句(不能有宏定义等,不常用);后缀大写的test.S文件,会进行预处理、汇编等操作,所以我们可以在这里面加入预处理的命令(比较常用)。
四、ARM汇编指令的格式
ARM汇编指令的格式比较固定、简单,即是:“操作码目标寄存器,操作数1,操作数2,……操作数n”。
例如将十六进制数0xaf放到寄存器r0中,我们用汇编代码可以这样写“mov r0,#0xaf”。这里的“mov”就是操作码(指令),实现的功能是将“0xaf”这个数存放到寄存器中“r0”中,“r0”也即是目标寄存器,“#0xaf”也即是操作数。在这里,“#0xaf”表示立即数(立即寻址方式指令中给出的数称为立即数立即数,亦即是直接参与运算不需处理的数),立即数需要用“#”来标识。
五、ARM指令分类
ARM指令可以分为程序状态寄存器操作指令、寄存器装载与存储指令、算术与逻辑指令、移位指令、乘法指令、比较指令、分支指令、浮点数指令、伪指令。
1、程序状态寄存器操作指令
程序状态寄存器操作指令包含msr、mrs两个指令。
(1)msr实现将通用寄存器(r0-r15)的值复制到状态寄存器(cpsr及spsr)中,用于更改处理器的工作模式及状态。例如,
MSR CPSR, R0 ;复制 R0 到 CPSR 中
MSR SPSR, R0 ;复制 R0 到 SPSR 中
(2)mrs实现将状态寄存器(cpsr及spsr)的值复制到通用寄存器(r0-r15)中,用于读取处理器的工作模式及状态。例如,
MRS R0, CPSR ; 复制 CPSR 到 R0 中
MRS R0, SPSR ; 复制 SPSR 到 R0 中
2、寄存器装载与存储指令
常用寄存器装载与存储指令操作指令包含ldr、str、ldm、stm四个指令,其中ldm、stm可以实现多个数据的传输。
(1)ldr实现将所在内存地址的值装载到寄存器中。
ldr rd, [rbase] ;rbase的值存储到rd寄存器
(2)str实现将寄存中的值存储在相应内存地址中。
str rd, [rbase] ;存储 rd 到 rbase 所包含的有效地址
(3)ldm实现将内存的值存储在相应寄存器地址中。常用于数据出栈。
ldmfd sp!, {r0-r3} ;将内存的数据出栈到r0-r3
(4)stm实现将寄存中的值存储在相应内存地址中。常用于数据入栈。
stmfd sp, {r0-r3} ;将寄存器r0-r3压栈
3、算术与逻辑指令
常用算术与逻辑指令包含and、orr、eor三个逻辑指令和adc、add、bic、mov、mvn、rsb、sub六个算术指令,共计九个指令。
(1)and实现逻辑与,相当于c语言中的位与。
and r0, r1, #0x01 ; r0=r1 & 0x01
(2)orr实现逻辑或,相当于c语言中的位或。
orr r0, r1, #0x01 ; r0=r1 | 0x01
(3)eor实现异或,相当于c语言中的异或。
eor r0, r1, #0x01 ; r0=r1 ^ 0x01
(4)bic实现清零操作。
bic r0, r0, #0x0f ; r0 = r0 & ~(0x0f)技术将低4bit清零
(5)mov实现数据的搬移,相当于c语言中的赋值(如果后面是立即数,这个立即数的范围一般是0~255(8bit))。
mov r0,#0x01 ; r0 = 0x01
mov r0,r1 ; r0 = r1
(6)mvn实现数据的取反并搬移,相当于c语言的取反在赋值。
mvn r0,#1 ; r0 = (~1) = -2
mvn r0,r1 ; r0 = (~r1) = -(r1 + 1)
(7)rsb实现反向减法。
rsb r0, r1, #20 ; r0 = 20 - r1
rsb r0, r1, r2 ; r0 = r2 - r1
(8)add实现算术加法。
add r0, r1, #0x01 ; r0 = r1 + 0x01
(9)sub实现算术减法。
sub r0, r1, #0x01 ; r0=r1 - 0x01
4、移位指令
移位指令包含lsl、asl两个左移指令和lsr、asr、ror、rrx四个右移指令,共计六个指令。
(1)lsl实现逻辑左移,与asl作用等同。
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, lsl#3 ;将r1左移3位,然后放入r0中
(2)asl实现算术左移,与lsl作用等同。
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, asl#3 ;将r1左移3位,然后放入r0中
(3)lsr实现逻辑右移。
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, lsr#3 ;将r1右移3位,然后放入r0中
(4)asr实现算术右移。
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, asr#3 ;将r1右移3位,然后放入r0中
(5)ror实现循环右移。
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, ror#3 ;将r1右移3位,移出的位放依次放到高位,然后放到r0中
(6)rrx实现带扩展的循环右移
mov r1, #0x21 ;将立即数0x21放到r1
mov r0, r1, rrx#3 ;将r1右移3位,移出的位放依次放到高位(借一位构成33bit),然后放到r0中
5、乘法指令
乘法指令操作指令包含mla、mul两个指令。
(1)mla是三目乘法指令(有三个操作数)。例如:
mla r0, r1,r2,r3 ;r0 = (r1 * r2) + r3
(2)mul是两目乘法指令(有两个操作数)。例如:
mul r0, r1, r2 ;r0 = r1 * r2
6、比较指令
常用比较指令包含cmp、cmn两个指令。
(1)cmp实现比较功能,例如,cmp r0, r1 ;r0-r1
(2)cmn实现取负的值的比较功能,例如,cmn r0, r1 ;r0-(-r1)
7、分支指令
分支指令包含b、bl两个指令。
(1)b为不带连接的分支指令,当程序从当前位置调到分支程序中执行,将无法再返回到当前位置的下一条指令处。例如:
.start
.loop
mov r0,#0x01 ;将0x01加载到r0寄存器
…………
b loop ; ;调到loop
mov r1,#0x01 ;将0x01加载到r1寄存器
程序将一直在loop这里循环(相当于while循环),“mov r1,#0x01”永远得不到执行。
(2)bl为带连接的分支指令(branch and link),跳转前把返回地址放入lr中,以便用于函数调当程序从当前位置调到分支程序中执行,执行完分支程序将再返回到当前位置的下一条指令处继续执行。例如:
.start
.loop
mov r0,#0x01 ;将0x01加载到r0寄存器
…………
bl loop ;调到loop
mov r1,#0x01 ;将0x01加载到r1寄存器
程序执行完loop后调到下一行的“mov r1,#0x01”处执行。
8、浮点数指令
浮点数指令很多,常用指令有abs、cmf、dvf、mvf、muf、ldf、suf、adf几个指令,用于处理浮点数的加载、加、减、乘、除。
9、伪指令
伪指令操作指令包含adr、adrl、align、dcx、equx、opt六个指令,用于描述指定汇编代码的对齐方式、宏定义等。
六、常用GNU伪指令
global _start @ 给_start外部链接属性
.section .text @ 指定当前段为代码段
.ascii .byte .short .long .word
.quad .float .string @ 定义数据
.align 4 @ 以4字节对齐
.balignl 16 0xabcdefgh @ 16字节对齐填充
.equ @ 类似于C中宏定义
.end @标识文件结束
.include @ 头文件包含
.arm / .code32 @声明以下为arm指令
.thumb / .code16 @声明以下为thubm指令
ldr 大范围的地址加载指令
adr 小范围的地址加载指令
adrl 中等范围的地址加载指令
nop 空操作
七、常用的8种寻址方式的指令实现
1、寄存器寻址
寄存器寻址就是利用寄存器中的数值作为操作数,是一种执行效率较高的寻址方式。例如:
mov r1, r2 ;r1 = r2
2、立即(立即数)寻址
立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就是在指令中给出,只要取出指令也就渠道了操作数。这个操作数被称为立即数,对应的寻址方式也叫作立即寻址。例如:
mov r0, #0xff00 ; r0 = 0xff00
3、寄存器移位寻址
将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。例如:
mov r0, r1, lsl #3 ; r0 = r1 << 3
4、寄存器间接寻址
寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。例如:
ldr r0, [r1] ;将内存地址存在r1这个寄存器中的内存地址里的值给r0
5、基址寻址
基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。常用于查表、数组操作、功能部件寄存器访问等。例如:
LDRR2,[R3,#0x0C];读取R3+0x0C地址上的内容,放入R2
6、块拷贝寻址
多寄存器传送指令用于将一块数据从存储器的某一位置拷贝到另一位置。例如,
STMIAR0!,{R1-R7} ;将R1~R7的数据保存到存储器中
7、多寄存器寻址
利用一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送多16个通用寄存器的值
ldmia r1!, {r2-r7, r12} ;一次访问多个寄存器
8、堆栈寻址
堆栈是一种数据结构,按先进后出的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。例子,
stmfd sp!, {r2-r7, lr}
八、常用指令后缀
同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
1、B(byte)功能不变,操作长度变为8位
2、H(half word)功能不变,长度变为16位
3、S(signed)功能不变,操作数变为有符号
4、条件执行后缀
5、“!”与“^”的作用
例如ldr可以加“b”、“h”、“s”变成 ldrb、ldrh、ldrsb、ldrsh用于表示加载8bit数据、16bit数据、有符号8bit数据、有符号16bit数据。
条件执行后缀,可以参看下表例。特点是条件后缀是否成立取决于当前代码的前面的代码,只影响当前代码的执行。
感叹号的作用就是r0的值在ldm过程中发生的增加或者减少后写回到r0去,也就是说ldm时会改变r0的值。例如,
ldmia r0, {r2 - r3} ;不会改变r0的值
ldmia r0!, {r2 - r3} ;会改变r0的值
^的作用是在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
ldmfd sp!, {r0 - r6, pc} ;spsr不会写入到cpsr
ldmfd sp!, {r0 - r6, pc}^ ;spsr写入到cpsr
九、ARM指令应用举例
这里就ARM应用举一个例子,使用ARM指令操作简单的硬件(点亮LED灯)。FS210开发板LED灯如下图所示,
要想控制这些器件,我们需要查看其对应的原理图。LED原理图如下
从原理图上可知,要想点亮两颗led灯,只需给GPC0_3及GPC0_4高电平即可。
打开S5PV210用户手册,找到GPIO章节的数据寄存器与控制寄存器的描述。结合GPIO寄存器的描述编写汇编程序。
#define GPC0_CON 0XE0200060 ;定义GPC0控制寄存器地址
#define GPC0_DAT 0XE0200064 ;定义GPC0数据寄存器地址
ldr r0,=GPC0_CON ;将GPC0_CON加载到r0
ldr r1,=0x11000 ;将0x11000加载到r1
str r1,[r0] ;将r1的值加载到r0寄存器
ldr r0,=GPC0_DAT ;将GPC0_DAT加载到r0
mov r1,#0x18 ;将立即数0x18加载到r1
str r1,[r0] ;将r1的值加载到r0寄存器
stop:
b stop ;死循环(相当于while(1))
交叉编译并下载到FS210开发板上运行,LED灯被点亮。