文章目录
- 零.预备知识
- 1.ARM与X86
- 2.ARM中指令的执行
- 3.ARM的九种寻址方式
- 立即数寻址
- 寄存器寻址
- 寄存器间接寻址
- 寄存器偏移寻址
- 寄存器基址变址寻址
- 批量寄存器寻址
- 相对寻址
- 堆栈寻址
- 块拷贝寻址
- 一.移位操作
- 二.寄存器装载和存储指令
- 1.LDR:装载单一数据
- 2.LDMIA:先减少,后装载
- 3.STR:存储单一数据
- 4.STMDB:先存储,后增加
- 三.算术和逻辑指令
- 1.MOV:传送
- 2.ADD:加法
- 3.SUB:减法
- 4.AND:逻辑与
- 5.ORR:逻辑或
- 6.BIC:位清除
- 四.比较指令
- 1.CMP:比较
- 五.跳转指令
- 1.B:直接跳转
- 2.BL:跳转且保存当前地址
ARM嵌入式开发中经常会涉及到汇编指令的知识,这里就总结一下最常用的几种ARM汇编指令。
零.预备知识
这里我们主要学习ARM的汇编指令,这些预备知识只是作为一个了解。
1.ARM与X86
要了解ARM,最好的是使用对比的方法。
ARM是一款32位的低功耗RSIC(精简指令集)微处理器。我们常了解的CPU可能就是办公中常用到的X86架构的计算机,X86使用的就是CSIC(复杂指令集),比如很有名的Intel处理器,下面就通过分析一下ARM架构与X86架构的区别来认识ARM:
项目 | ARM | X86 |
指令集 | RSIC精简指令集 | CSIC复杂指令集 |
功耗 | 低功耗 | 超高性能 |
用途 | 移动端的老大 | PC端的老大 |
解码 | 并行 | 并行 |
ARM的特点是:
- 体积小、低功耗、高性能
- 支持Thumb(16位)和ARM(32位)双指令
- 指令执行效率高
- 寻址方式简单
- 指令长度固定
2.ARM中指令的执行
由于ARM采用RSIC架构,所以CPU本身不能直对内存进行操作,而是先将内存中数据加载到CPU中的寄存器,然后对寄存器中的值进行处理。
ARM中指令的执行遵循流水线形式,众所周知,CPU执行指令都是从内存中进行取指令、分析指令、执行指令的。一阶段持从内存中取回的指令,第二阶段开始解码,而第三阶段实际执行它。故此,程序计数器总是超出当前执行的指令两个指令。
ARM处理指令时,比如要连续执行三条指令:a、b、c,那么ARM在执行a指令的同时,已经在解析b指令了,同时有在内存中取c指令。所以pc寄存器中的地址一般是当前指令地址+8(Byte),因为ARM是32位CPU,所以一次处理的指令是4字节,所以第三条指令的地址就是当前指令的地址+8。
在大概说一下RSIC吧,RSIC将ARM要执行的操作以最基本的指令实现。换句话说,如果人们是在RSIC指令集下执行跑步时,大脑(CPU)对人体发出的指令就是:先迈左脚、再迈右脚、再迈左脚、再迈右脚…将跑步这条指令细化为每一个最基本的操作。如果是CSIC(复杂指令集时),大脑只需要对人体发出指令:跑步,就可以了。
3.ARM的九种寻址方式
寻址方式就是CPU根据指令中的地址信息,找出物理地址也就是内存地址的方式,通俗理解就是ARM指出内存地址的方式。
寻址的目的就是找出操作数,比如ARM要做一个除法运算,就需要除数和被除数,除数和被除数都是除法指令的操作数,要找到这些操作数,可以有多种方法,寻找操作数的过程就叫做寻址。(我个人理解)
ARM支持九种寻址方式:
- 立即数寻址
- 寄存器寻址
- 寄存器偏移寻址
- 寄存器间接寻址
- 寄存器基址变址寻址
- 多寄存器寻址
- 相对寻址
- 堆栈寻址
- 块拷贝寻址
立即数寻址
立即数寻址就是直接将内存中的数据发给CPU作为操作数。注意,由于ARM是32位指令集,所以立即数的范围不可以超出0255,也就是说立即数的范围只能是0255。
格式:就是在立即数前面加上 # 来作为操作数
典型的例子就是直接对寄存器进行写值:
ldr r0, #254 ;将254写入r0寄存器
add r1, r2, #3 ;将r2寄存器中的值与3相加后,在写入r1寄存器
寄存器寻址
寄存器寻址就是直接将寄存器中的数值作为操作数:
ldr r1, r0 ;将r0寄存器中的值写到r0
add r3, r2, r1 ;将r1、r2寄存器的值相加,结果写入r3寄存器
寄存器间接寻址
还是利用了寄存器,只不过操作数不是寄存器中的值了,操作数在内存中,那怎么办?没事,操作数的地址就在寄存器中。所以寄存器间接寻址相当于以寄存器中的值作为内存地址,去内存中寻找操作数。
格式:在提供操作数地址的寄存器上加上[],比如[r0]
mov r0, #0X54000032
ldr r1, [r0] ;将地址为0X54000032的数据写入r1寄存器中
寄存器偏移寻址
以寄存器寻址为本,将寄存器中的数移位后作为操作数。
一共有6中移位操作:
LSL:逻辑左移(Logical Shift Left),寄存器中字的低端空出的位补0。
LSR:逻辑右移(Logical Shift Right),寄存器中字的高端空出的位补0。
ASL:算术左移(Arithmetic Shift Left),和逻辑左移LSL相同。
ASR:算术右移(Arithmetic Shift Right),移位过程中符号位不变,即如果源操作数是正数,则字的高端空出的位补0,否则补1。
ROR:循环右移(Rotate Right),由字的低端移出的位填入字的高端空出的位。
RRX:带扩展的循环右移(Rotate Right eXtended),操作数右移一位,高端空出的位用进位标志C的值来填充,低端移出的位填入进位标志位。
格式:rx, 移位命令 移位操作数
ldr r0, r1, lsl #3 ;将r1的值逻辑左移3位后写入r0
ldr r0, r1, ror r2 ;将r1的值循环右移r2中的值对应位后,写入r0
寄存器基址变址寻址
基址变址寻址是基于寄存器间接寻址的,只不过地址不再是寄存器中的值了,而是偏移后的值,这里的偏移值可以理解为地址相加值。
加上感叹号应该有优先执行的意思吧(个人理解)
格式:[rx, n],表示在rx寄存器所指向的地址上,再偏移(相加)n字节
ldr r0, [r1, #3] ;地址为:r1值+3字节,指令执行完r1不变
ldr r0, [r1, #3]! ;地址为:r1值+3字节,指令执行完r1+3
ldr r0, [r1, #-1] ;地址为:r1值-1字节,指令执行完r1不变
ldr r0, [r1, r2] ;地址为:r1值+r2值
ldr r0, [r1], #4 ;地址为:r1值,但指令执行完后,r1值+4字节
批量寄存器寻址
批量寄存器寻址就是使用一个大括号{}包含多个寄存器
ldmia r0, {r1, r2, r3, r4} ;将r1,r2,r3,r4中的数据依次放入R0指向的内存地址,r0+4指向的内存地址...
ldmia r0, {r1-r4} ;同上。
注意,作为存储地址时,高编号的寄存器存放在高地址
高编号寄存器存放在高地址:
所以,写内存的时候(默认从高地址往下写),优先操作高编号寄存器
相反,读内存的时候(默认从低地址往上读),优先操作低编号寄存器
相对寻址
通过标号进行寻址,经常与跳转指令相配合使用
bl heihei
heihei: ;跳转到heihei执行
堆栈寻址
堆栈即Stack,因为CPU的寄存器总是及其有限的,很多时候我们不得不使用内存来存储数据,比如进行多级跳转的时候,这时候堆栈就是一个很好的工具,每次跳转就将当前函数的返回地址存储到内存,最底层被调用的子函数会最先返回,就先将压入栈的现场返回,以此类推…,ARM使用SP(R13)作为栈指针,ARM设计的内存栈模型有2×2=4种
按照栈在内存增长的方向分为递增栈和递减栈:
**递增(Increase)**堆栈:向堆栈写入数据时,堆栈由低地址向高地址生长。
**递减(Descend)**堆栈:向堆栈写入数据时,堆栈由高地址向低地址生长。
根据堆栈指针SP指向的位置,又可以把堆栈分为满堆栈和空堆栈两种。
满堆栈(Full Stack):SP始终指向栈顶元素,压栈的时候先移动SP,再将数据放入SP指向的地址。
空堆栈(Empty Stack):SP始终指向下一个将要放入元素的位置,压栈时先将数据放入SP指向的地址,再移动SP
最后,可以得到4种基本的堆栈类型:
满增栈(FA):堆栈指针指向最后压入的数据,且由低地址向高地址生长。
满减栈(FD):堆栈指针指向最后压入的数据,且由高地址向低地址生长。常用这种
空增栈(EA):堆栈指针指向下一个将要压入数据的地址,且由低地址向高地址生长。
空减栈(ED):堆栈指针指向下一个将要压入数据的地址,且由高地址向低地址生长。
stmfd sp!, {r1-r7, lr} ;将r1到r7和lr的数据压入fd栈
块拷贝寻址
块拷贝寻址提供了一块内存和一组寄存器之间的拷贝,按照内存使用方式的不同,可以分为2×2=4种。地址增方向/地址减方向×先偏移/后偏移。堆栈寻址就可以看作是块拷贝寻址的的一个实例。
即:
IB:Increment Before Operating
IA:Increment After Operating
DB:Decrement Before Operating
DA:Decrement After Operating
STMIA R0!,{R1—R7} ;将R1-R7的寄存器中的值放入R0指向的地址,R0自动更新,指向操作后的地址
参考文章
一.移位操作
移位操作在ARM中不可以作为一个单独的指令使用,移位操作只是指令格式中的一个字段。
最常用的就是逻辑移位了,遵循左乘右除的法则。
二.寄存器装载和存储指令
寄存器装载指令和寄存器存储指令是控制:寄存器和内存之间的交互的。
一般的汇编指令都是操作寄存器的,然鹅寄存器又要与内存进行数据交互,所以就需要有汇编指令在寄存器与内存之间扮演搬运工的角色了。
当需要装载存储多个数据时,使用LDMxx和STMxx指令如下:
最常用的一组是:LDMIA和STMDB(俩个相对应)
1.LDR:装载单一数据
LDR是寄存器装载指令,可以从内存地址中读取数据,写到指定寄存器中.
格式为:LDR{条件} Rd, <地址>
例如:
LDR R0, [R1] ;将r1中对应地址的数据写到r0
LDR R0, =0X54000056 ; 将0X54000056写到r0
2.LDMIA:先减少,后装载
IA:先装载,后增加,经常配合栈指针来使用:
LDMIA sp, {fp, sp, pc}
而且对于批量寄存器寻址{},遵循高编号寄存器存放在高内存地址,而fp、ip、sp、lr、pc四个寄存器的编号分别为:11、12、13、14、15,所以LDMIA的操作顺序就是pc、sp、fp
所以指令的指向过程如下:
sp指向的地址-4字节
将pc地址的数据写到sp
sp指向的地址-4字节
将sp地址的数据写到sp
sp指向的地址-4字节
将fp地址的数据写到sp
3.STR:存储单一数据
STR是存储指令,由于将寄存器中的数据存储到指定内存中
格式为:STR{条件} Rd, <内存>
例如:
Rbase表示基地址寄存器,Rindex表示变址寄存器,index表示偏移量
STR Rd, [Rbase] ;将Rd的值写到Rbase包含的地址中
STR Rd, [Rbase, Rindex] ;将Rd的值写到Rbase+Rindex(偏移后)所包含的地址中
STR Rd, [Rbase, #index] ;将Rd的值写到Rbase包含地址偏移index后的地址中
STR Rd, [Rbase, Rindex]! ;把新地址写回Rbase
STR Rd, [Rbase, #index]! ;把新地址写回Rbase
STR Rd, [Rbase], Rindex ;把Rd的值写到Rbase包含的地址中,再将Rbase+Rindex后的地址写入Rbase中
STR Rd, [Rbase, Rindex, LSL #2]
;将Rd的值写入Rbase+(Rindex*4)后的地址中,LSL为左移,左乘右除,左移2位代表乘以4
4.STMDB:先存储,后增加
STMDB(默认选项)!感叹号代表取最终被修改的结果
高编号寄存器存放在高地址,
所以,写内存的时候(默认从高地址往下写),优先操作高编号寄存器
相反,读内存的时候(默认从低地址往上读),优先操作低编号寄存器
STMDB sp!, {fp, ip, lr, pc}
其中!代表sp的值是最终的结果。而且对于批量寄存器寻址{},遵循高编号寄存器存放在高内存地址,而fp、ip、lr、pc四个寄存器的编号分别为:11、12、14、15,所以STMDB的操作顺序就是fp、ip、lr、pc
先将pc写入sp指向的地址
sp-4字节
再将lr写入sp指向的地址
sp-4字节
再将ip写入sp指向的地址
sp-4字节
再将fp写入sp指向的地址
sp最终为sp-12
三.算术和逻辑指令
1.MOV:传送
MOV可以将一个寄存器(也可以是配合移位操作的寄存器)的值传送到另一个寄存器中,相当于复制寄存器的值,当然传送的对象不仅可以是寄存器,也可以是数值,例如:
MOV R0, R1 ;将R1的值传送到R0
MOV R0, =123 ;将123写入R0
MOV R0, R1, LSL #3 ;将R1*8后的值写入R0
2.ADD:加法
ADD就是将俩个操作数相加,将结果写入指定寄存器中,例如:
ADD R0, R1, R2 ;R0=R1+R2
ADD R0, R1, #255 ;R0=R1+255
ADD R0, R1, R2, LSL #2 ;R0=R1+(R2*4)
3.SUB:减法
SUB将俩个操作数做减法,将结果写入指定寄存器中,例如:
SUB R0, R1, R2 ;R0=R1-R2
SUB R0, R1, #255 ;R0=R1-255
SUB R0, R1, R2, LSL #2 ;R0=R1-(R2*4)
4.AND:逻辑与
AND将俩个操作数进行逻辑与操作,将结果写入目的寄存器中,操作数可以是:寄存器、被移位的寄存器、立即数,例如:
AND R0, R0, #2 ;只保留R0中数据的1位
5.ORR:逻辑或
ORR使用方法与AND一样。
6.BIC:位清除
BIC可以定点清除寄存器中数据的某一位,其作用原理与掩码类似,操作数2是一个32位掩码,例如:
BIC R0, R0, #%111011 ;清除2位上的数据
四.比较指令
1.CMP:比较
CMP指令可以用来比较俩个操作数的区别,将结果以更新CPSR寄存器相关的条件标志位,后期通过判断相关位来了解相同还是不相同。
例如:
CMP R0, R1 ;判断R0值与R1值是否相同
CMP R0, #5 ;判断R0值是否为5
五.跳转指令
ARM汇编中的跳转可以有俩种实现方式,第一种就是利用跳转指令。第二种就是直接向程序计数器PC中写入要跳转的地址,这样可以实现任意地址的跳转
1.B:直接跳转
B是最简单的分支,遇到B指令后,ARM就会跳转到B指定的地址进行执行,这个跳转指令没有返回值,一旦跳了就不可回头。
例如:
B Hei ;跳转到标号Hei处执行
2.BL:跳转且保存当前地址
BL也是跳转指令,与B不同的是,BL指令跳转时,会将当前的地址存储在R14(LR)寄存器中,当执行完调用子程序时,还可以跳回原程序处继续执行。
例如:
BL Hei ;跳转到标号Hei处执行,同时将当前地址保存在LR寄存器中