嵌入式ARM汇编详解 | 您所在的位置:网站首页 › 汇编中sub › 嵌入式ARM汇编详解 |
ARM嵌入式开发中经常会涉及到汇编指令的知识,这里就总结一下最常用的几种ARM汇编指令。 零.预备知识 这里我们主要学习ARM的汇编指令,这些预备知识只是作为一个了解。 1.ARM与X86 要了解ARM,最好的是使用对比的方法。 ARM是一款32位的低功耗RSIC(精简指令集)微处理器。我们常了解的CPU可能就是办公中常用到的X86架构的计算机,X86使用的就是CSIC(复杂指令集),比如很有名的Intel处理器,下面就通过分析一下ARM架构与X86架构的区别来认识ARM: 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寄存器中 |
CopyRight 2018-2019 实验室设备网 版权所有 |