嵌入式ARM汇编详解
文章目录
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寄存器中
嵌入式ARM汇编详解的更多相关文章
- 嵌入式linux性能详解_转
最近简单看了下<嵌入式Linux性能详解>一书,对系统内存分布测试.程序运行.动态库等都很很好的解析. 作者史子旺,loughsky@sina.com. 有时间希望仔细通读,并验证.
- ARM指令集详解--汇编
1. 汇编 1.1. 通用寄存器 通用寄存器 37个寄存器,31个通用寄存器,6个状态寄存器,R13堆栈指针sp,R14返回指针,R15为PC指针, cpsr_c代表的是这32位中的 ...
- gcc内嵌汇编详解
[作者:byeyear 首发:cnblogs Email:east3@163.com 转载请注明] 有时候我们希望在C/C++代码中使用嵌入式汇编,因为C中没有对应的函数或语法可用. ...
- hibernate的@EmbeddedId嵌入式主键详解
一.前言 在我们的日常开发中,有时候会用到数据库进行设计的时候,采用了复合主键来来保证唯一性,下面介绍一下采用hibernate的@EmbeddedId嵌入式主键. 二.说明 设计一个学生类,包含了三 ...
- stdcall、cdecl、fastcall、thiscall 、naked call的汇编详解
函数调用规范 当高级语言函数被编译成机器码时,有一个问题就必须解决:因为CPU没有办法知道一个函数调用需要多少个.什么样的参数.即计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者 ...
- ARM指令集详解
一.跳转指令 B: 跳转指令 BL: 带返回的跳转指令 BLX: 带返回和状态切换的跳转指令 BX: 带状态切换的跳转指令 二.数据处理指令 1.MOV:数据传送指令 MOV{条件}{S} 目的 ...
- 嵌入式nand flash详解
一.s3c2440启动后会将nand flash的前4K程序复制到内部的sram中,这个过程是硬件自动完成的,但是如果我们的程序远远大于4K,这个时候就需要将程序从flash拷贝到内存中来运行了. 二 ...
- 2.3 ARM寄存器详解
一共有37个寄存器 1. 31个通用寄存器 2. 6个状态寄存器 R13作为堆栈指针 R14链接寄存器 1.保存函数返回地址 2. 异常返回地址 R15程序计数器(PC指针) 程序状态寄存器 只有在异 ...
- 022 ARM寄存器详解
R13:堆栈指针寄存器 SP R14:链接寄存器 LR R15:程序计数器 PC指针 CPSR:当前程序状态寄存器 SPSR:备份程序状态寄存器
随机推荐
- Linux守护进程列表/守护进程
在linux或者unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统. ...
- 如何使用Linux lsblk命令列出块设备信息
译至:http://linoxide.com/linux-command/linux-lsblk-command/ lsblk命令(列出块设备)用于列出所有可用的块设备的信息,但是, 它并没有列出有关 ...
- Jmeter之cokie管理器和http默认值
根据上文中,都配置好以后就可以开始启动了,点击启动,线程组里面就会开始记录你的网页请求.这里使用CNode网站做测试.我分别记录进行了"登入","话题创建",& ...
- 【重学Java】IO流
IO流的UML类图 File类 File类概述和构造方法[应用] File类介绍 它是文件和目录路径名的抽象表示 文件和目录是可以通过File封装成对象的 对于File而言,其封装的并不是一个真正存在 ...
- WPF教程七:通过App.xaml来了解Application类都能干什么
这个章节来了解Application类,我考虑了一晚上决定跳过控件类相关的学习,因为控件如果只是入门的话每个控件F12跳过去看一下属性.事件就能大致了解的差不多,而且控件比较多,每个都这样看一遍,感觉 ...
- mDNS知识
1.域名系统(Domain Name System,缩写:DNS)是互联网的一项服务.它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网.DNS使用TCP和UDP端口53. ...
- PXE高效批量网络装机
PXE高效批量网络装机 一.PXE概述 1)PXE (Preboot eXcution Environment) ...
- React组件三大属性之 refs
React组件三大属性之 refs refs属性 1) 组件内的标签都可以定义ref属性来标识自己 a. <input type="text" ref={input => ...
- 动态 WebApi 引擎使用教程(3行代码完成动态 WebApi 构建)
目录 什么是 WebApiEngine? 开源地址 使用方法 使用 [ApiBind] 标签让任何方法变成 WebApi 对 API 进行分类 自定义 API 名称 复制特性 为整个类配置 WebAp ...
- 每天五分钟Go - 循环语句
带条件的for循环 for init; condition; post { } 示例代码 for i:=0;i<10;i++{ fmt.Println("current:", ...