ARM Cortex M3指令集
一、跳转指令
跳转指令用于实现程序流程的跳转,在ARM 程序中有两种方法可以实现程序流程的跳转:
Ⅰ.使用专门的跳转指令。
Ⅱ.直接向程序计数器PC 写入跳转地址值。
通过向程序计数器PC 写入跳转地址值,可以实现在4GB 的地址空间中的任意跳转,在跳转之前结合使用 MOV LR,PC 等类似指令,可以保存将来的返回地址值,从而实现在4GB 连续的线性地址空间的子程序调用。
ARM指令集中的跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转,包括以下4 条指令:
1、 B 指令
B 指令的格式为:
B{条件} 目标地址
B 指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB 的地址空间)。以下指令:
B Label ;程序无条件跳转到标号Label 处执行
CMP R1,#0 ;当CPSR 寄存器中的Z 条件码置位时,程序跳转到标号Label 处执行
BEQ Label
2、 BL 指令
BL 指令的格式为:
BL{条件} 目标地址
BL 是另一个跳转指令,但跳转之前,会在寄存器R14 中保存PC 的当前内容,因此,可以通过将R14 的内容重新加载到PC 中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令:
BL Label ;当程序无条件跳转到标号Label 处执行时,同时将当前的PC 值保存到R14 中
3、 BLX 指令
BLX 指令的格式为:
BLX 目标地址
BLX 指令从ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14 值复制到PC 中来完成。
4、 BX 指令
BX 指令的格式为:
BX{条件} 目标地址
BX 指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM 指令,也可以是Thumb 指令。
ARM7TDMI(-S)具有32位ARM指令集和16位Thumb指令集。
ARM指令集效率高,但是代码密度高
Thumb指令集具有较高的代码密度,却仍然保持着ARM的大多数性能上的优势,它是ARM的子集。
所有的ARM置零都是可以条件执行的,而Thumb置零仅有一条指令具备条件执行的功能。
ARM和Thumb程序可以相互调用,相互之间状态切换开销几乎为零。
首先我们来看一下分类:
一、数据处理指令操作数寻址方式
1、立即数寻址方式
2、寄存器寻址方式
3、寄存器移位寻址方式
二、存储器访问指令操作数寻址方式
1、寄存器间接寻址
2、基址变址寻址
3、相对寻址
4、多寄存器寻址(块拷贝寻址)
5、堆栈寻址
下面来一一介绍:
ARM有9种寻址方式
寻址方式:
1、立即寻址:
立即寻址指令中的操作码字段后面的部分就是操作数本省,也就是说,数据就包含再指
令,取指令也就取出了可以立即使用的操作数。举例:
SUBS R0,R0,#1 ;R0减1,结果放入R0中,并且影响标志位
MOV R0,#0xFF000 ;将立即数0xFF000装入R0寄存器
ARM规定:这个立即数必须符合8位图格式,负责必须使用“文字池”方式,通过存储器访问指令加载,所谓的8位图格式就是指,这个数据能通过一个8bit的数循环右移偶数位得到。
2、寄存器寻址:
操作数的值在寄存器中,指令中的地址字段指出的是寄存器的编号,指令执行的时候直
出寄存器值来操作。举例:
MOV R1,R2 ;将R2的值存入R1
MOV R0,R1,R2 ;将R1的值减去R2的值,结构存到R0
3、寄存器移位寻址
寄存器移位寻址是ARM处理器特有的寻址方式。当第二个操作数是寄存器移位方式时,
第二个操作数在与第一个操作数结合之前,选择进行移位操作。举例:
MOV R0,R2,LSL #3 ;R2的值左移3位,结果放入R0,即R0=R2*8
ANDS R1,R1,R2,LSL R3 ;R2的值左移R3位,然后和R1相"与"操作,结构放入R1
可采用移位操作的指令如下:
LSL左移、LSR右移、ASR算术右移、ROR循环右移、RRX带扩展的循环右移
4、寄存器间接寻址:
寄存器间接寻址指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存
再寄存器指定地址的存储单元中,即寄存器为操作数的地址指针,举例:
LDR R1,[R2] ;将R2指向的单元中的数据保存再R1中
SWP R1,R1,[R2] ;将寄存器R1的值和R2指向的单元中进行内容交换
5、基址变址寻址:
基址寻址就是将基址寄存器的内容与指令中给出的偏移量进行相加,形成操作数的有效
地址。
基址寻址用于访问基址附近的存储单元,常用于查表、数组操作以及功能不见寄存器访
问等 举例:
LDR R2,[R3,#0x0c] ; 读取R3 0x0c地址指向的存储单元的内容,放入R2
STR R1,[R0,#-4]! ;先R0=R0-4,然后把R1的值寄存到R0所指向的单元中
LDR R1,[R0,R3,LSL #1] ;将R0 R3*2地址上的单元的内容读出,并存入R1中
6、相对寻址:
相对寻址是基址寻址的一种变通。由程序计数器PC提供基址地址,指令中的地址码字段为偏移量。两者相加后得到的地址即为操作数的有效地址。
7、多寄存器寻址:
多寄存器寻址即一次可传送几个寄存器的值,允许一条指令传送16个寄存器的任何子集或
所有的存储器,举例:
LDMIA R1!,{R2-R7,R12};将R1指向的单元中的数据读出到R2---R7、R12中(R1自自动 )
STMIA R0!,{R2-R7,R12};将R2---R7、R12中的数据一次读入到R0指向的单元中(R0自动 )
8、堆栈寻址(块拷贝寻址):
堆栈是一种按特定顺序进行存取的存储区,操作顺序分为“先进后出”和“后进先出”,堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向的存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。
存储器堆栈可以分为两种:
向上生长:递增堆栈
向下生长:递减堆栈
还有从当前堆栈指针指向的内容是否有效可以分为:满递增、空递增、满递减、空递减
举例:
STMFD SP!,{R1-R7,LR} ;将R1---R7、LR入栈。满递减堆栈
LDMFD SP!,{R1-R7,LR} ;数据出栈,放入R1---R7、LR寄存器,满递减堆栈
ARM指令的基本格式
ARM指令的基本格式为:
<Opcode> {<Cond>} {S} <Rd> , <Rn> { , <Opcode2> }
其中,< >内的项是必需的,{ }内的项是可选的。
(1)Opcode项
Opcode是指令助记符,即操作码,说明指令需要执行的操作,在指令中是必需的。
(2)Cond项(command)
Cond项表明了指令的执行的条件,每一条ARM指令都可以在规定的条件下执行,每条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。条件码共有16种,每种条件码用2个字符表示,这两个字符可以添加至指令助记符的后面,与指令同时使用。当指令的执行条件满足时,指令才被执行,否则指令被忽略。如果在指令后不写条件码,则使用默认条件AL(无条件执行)。
指令的条件码
条 件 码 |
助记符后缀 |
标 志 |
含 义 |
0000 |
EQ |
Z置位 |
相等equal |
0001 |
NE |
Z清零 |
不相等not equal |
0010 |
CS |
C置位 |
无符号数大于或等于Carry Set |
0011 |
CC |
C清零 |
无符号数小于 |
0100 |
MI |
N置位 |
负数minus |
0101 |
PL |
N清零 |
正数或零plus |
0110 |
VS |
V置位 |
溢出 |
0111 |
VC |
V清零 |
没有溢出 |
1000 |
HI |
C置位Z清零 |
无符号数大于high |
1001 |
LS |
Z置位C清零 |
无符号数小于或等于less |
1010 |
GE |
N等于V |
带符号数大于或等于 |
1011 |
LT |
N不等于V |
带符号数小于least |
1100 |
GT |
Z清零且(N等于V) |
带符号数大于great |
1101 |
LE |
Z清零或(N不等于V) |
带符号数小于或等于 |
1110 |
AL |
忽略 |
无条件执行all |
1111 |
|
|
|
条件码应用举例:
例:比较两个值大小,并进行相应加1处理,C语言代码为:
if ( a > b ) a++;
else b++;
对应的ARM指令如下(其中R0中保存a 的值,R1中保存b的值):
CMP R0, R1 ; R0与R1比较,做R0-R1的操作
ADDHI R0, R0, #1 ;若R0 > R1, 则R0 = R0 + 1
ADDLS R1, R1, #1 ; 若R0 <= R1, 则R1 = R1 + 1
CMP比较指令,用于把一个寄存器的内容和另一个寄存器的内容或一个立即数进行比较,同时更新CPSR中条件标志位的值。指令将第一操作数减去第二操作数,但不存储结果,只更改条件标志位。
CMP R1, R0 ;做R1-R0的操作。
CMP R1,#10 ;做R1-10的操作。
(3) S项(sign)
S项是条件码设置项,它决定本次指令执行的结果是否影响至CPSR寄存器的相应状态位的值。该项是可选的,使用时影响CPSR,否则不影响CPSR。
(4)Rd项(destination)
Rd是指令中的目标寄存器,它是必需的。根据指令的不同,有些指令中要求Rd必须有R0~R7之间,有些要求Rd必须在R0~R14之间,有些则没有特殊要求。
(5)Rn项
Rn是第一个操作数的寄存器,和Rd一样,不同的指令对其的使用有不同的要求。
(6)Opcode2项
Opcode2项是第二个操作数,在ARM指令中,该操作数有三种形式:立即数形式、寄存器Rm形式和寄存器加移位形式(Rm, shift)。
例如:
SUB R3, R1, #10
SUB R3, R1, R2
SUB R3, R1, R2, LSL #2
SUB R3, R1, R2, LSL R0
ARM指令详解
ARM指令集可分为以下6类:
l 跳转指令
l 数据处理指令
l 程序状态寄存器(PSR)处理指令
l 加载/存储指令
l 协处理器指令
l 异常产生指令
ARM指令及功能描述
助 记 符 |
指令功能描述 |
ADC |
带进位加法指令 |
ADD |
加法指令 |
AND |
逻辑与指令 |
B |
跳转指令 |
BIC |
位清零指令 |
BKPT |
软件断点 |
BL |
带返回的跳转指令 |
BLX |
带返回和状态切换的跳转指令 |
BX |
带状态切换的跳转指令 |
CDP |
协处理器数据操作指令 |
CMN |
取反比较指令 |
CMP |
比较指令 |
EOR |
逻辑异或指令 |
LDC |
存储器到协处理器的数据传输指令 |
LDM |
加载多个寄存器的指令 |
LDR |
存储器到寄存器的数据传输指令 |
MCR |
从ARM寄存器到协处理器寄存器的数据传输指令 |
MLA |
乘加运算指令 |
MOV |
数据传输指令 |
MRC |
从协处理器寄存器到ARM寄存器的数据传输指令 |
MRS |
传送CPSR或SPSR的值到通用寄存器的指令 |
MSR |
传送通用寄存器的值到CPSR或SPSR的指令 |
MUL |
32位乘法指令 |
MVN |
数据取反传送指令 |
ORR |
逻辑或指令 |
RSB |
反向减法指令 |
RSC |
带借位的反向减法指令 |
SBC |
带借位的减法指令 |
STC |
协处理器寄存器写入存储器指令 |
STM |
存储多个寄存器的值到存储器指令 |
STR |
存储寄存器的值到存储器的指令 |
SUB |
减法指令 |
SWI |
软件中断指令 |
SWP |
寄存器与存储器 或 寄存器与寄存器之间的数据交换指令 |
TEQ |
相等测试指令 |
TST |
位测试指令 |
1. 跳转指令
用于实现程序流程的跳转,在ARM程序中有两种方法可以实现程序流程的跳转:一是使用专门的跳转指令,二是直接向程序计数器PC写入跳转地址值。第二种方法可以实现在4GB的地址空间中的任意跳转,在跳转之前结合使用“MOV LR , PC”等类似指令,可以保存将来的返回地址值,从而实现在4GB连续的线性地址空间的子程序调用。
1)ARM指令集中的跳转指令可以实现从当前指令向前或向后的32MB的地址空间的跳转。
l B指令
格式:
B{条件} 目标地址
注意,存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(相对寻址)。这个偏移量是一个24位的有符号数,左移两位后表示的有效偏移为26位(前后32MB的地址空间)。{}表示可以省略。
如:B Label 程序无条件跳转到标号Label处执行。
CMP R1,#0
BEQ Label
当CPSR寄存器中的Z条件码置位时,程序跳转到Label处执行。
当前PC:是指跳转指令本身的起始地址。
l BL指令
格式:
BL{条件} 目标地址
这条指令在跳转之前,会在寄存器R14中保存当前的下一条指令的地址,因此,可以通过将R14重新加载到PC中,来返回到跳转指令之后的那条指令处执行。该指令是实现子程序调用的一种常用手段。
l BX 指令
格式:
BX {条件} 目标地址
BX指令中所指定的目标地址,只能使用寄存器的寻址方式,即跳转的目标地址应先保存在一个寄存器中。指令在实现跳转的同时,完成处理器的工作状态的切换(ARM状态与Thumb状态间的切换)。
BX指令中,用寄存器的最低位来指示切换到哪一个工作状态。如寄存器最低位为1,则把目标地址处的代码解释为Thumb代码,进入Thumb工作状态,并自动将CPSR中的控制位T置1。若寄存器最低位为0,则把目标地址处的代码解释为ARM代码,进入ARM工作状态,并自动将CPSR中的控制位T置0。
…
ADRL R0, ThumbFun + 1 ;生成分支地址并置最低位为 1
BX R0 ;跳转到R0所指定的地址,并切换处理器到Thumb工作状态
…
ThumbFun
… ;Thumb汇编指令
…
| BLX指令
以上两条指令的综合。
2. 数据处理指令
数据处理指令可分数据传送指令、算术逻辑运算指令和比较指令等。数据传送指令用于在寄存器和存储器之间进行数据的双向的传输。所有ARM数据处理指令均可选择使用S后缀,以影响状态标志CPSR。比较指令(CMP、CMN、TST、TEQ)不保存运算结果,这些指令也不使用S后缀,但会直接影响CPSR中的相应的状态标志位。
(1)数据传送指令MOV 和MVN
格式:
MOV {条件} {S} 目的寄存器, 源操作数
MOV指令可以完成从另一个寄存器、被移位的寄存器、或将一个立即数加载到目的寄存器。与MVN指令不同的是在传送之前,将被传送的对象先按位取反,再传送到目的寄存器。
例:MVN R1 , #0XFF ;R1 ← 0X FFFF FF00
MVN R1 , R2
MOV PC,R14;将寄存器R14的值传送给PC,用于子程序返回。
(2) 数据比较指令CMP , CMN , TST , TEQ
例:
CMP R1, R2 ; 做R1 – R2 的操作,结果不保存,但影响标志位。
CMP R1 , #10 ;做R1 - 10的操作,结果不保存,但影响标志位。
CMN 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数取反后进行比较操作,根据运算结果影响CPSR中的标志位。该指令实际完成操作数1和操作数2相加,并根据结果更改条件标志位。
TST位测试指令,用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数1是要测试的数,而操作数2 是一个位掩码,该指令一般用来检测是否设置了特定的位。
TST {条件} 操作数1, 操作数2
例:TST R0, #0X0000 0040 ; 指令用来测试R0的位3是否为1。
TST指令通常和EQ、NE条件码配合使用,当所有测试位为0时,EQ有效,而只要有一个测试位不为0,则NE有效。
TEQ相等测试指令,用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中的条件标志位。指令用于比较两个操作数是否相等。如果相等,则 Z = 1,否则Z = 0。指令通常和EQ、NE条件码配合使用
例:TEQ R1, R2
TST R1,#%1;测试R1中是否设置了最低位(%表示二进制数)
(3)逻辑运算类指令:AND、ORR、EOR、BIC
格式:逻辑类指令 {条件} {S} 目的寄存器,操作数1, 操作数2
S选项,说明运算结果影响CPSR的条件标志位,没有S选项,则不影响CPSR的条件标志位。
操作数1应该是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器或一个立即数。
AND指令常用于将操作数1的某个位置0;ORR指令常用于将操作数1的某个位置1;EOR(异或)指令常用于将操作数1的某个位取反。与0相异或,保持不变,与1相异或,则取反。BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。
例:BIC R1, R1, #0X0F ;将R1的低四位清零,其他位不变。
(4)算术运算类指令:ADD、ADC、SUB、SBC、RSB、RSC
格式:算术运算类指令 {条件} {S} 目的寄存器,操作数1,操作数2
目的寄存器,操作数1和操作数2使用的寄存器必须在R0~R7之间。
操作数1应该是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器或一个立即数。
例:ADDS R1, R1,#10 ;结果影响标志位
ADD R1, R1, R2 ;结果不影响标志位
ADD R3, R1, R2, LSL #2 ; R3 = R1 + ( R2 << 2 )
ADD指令完成的功能是将操作数1加上操作数2,结果送到目的寄存器。
ADC指令完成的功能是将操作数1加上操作数2,再加上标志位C的值,结果送到目的寄存器。
SUB指令完成的功能是将操作数1减去操作数2,结果送到目的寄存器。
SBC指令完成的功能是将操作数1减去操作数2,再减去标志位C的取反值,结果送到目的寄存器。
RSB逆向减法指令完成的功能是将操作数2减去操作数1,结果送到目的寄存器。
RSC带借位的逆向减法指令完成的功能是将操作数2减去操作数1,再减去标志位C的取反值,结果送到目的寄存器。
例:
SUB R0, R1, #256 ;R0 = R1 - 256 , 结果不影响标志位
SUBS R0, R2,R3,LSL #1 ;R0 = R2 - ( R3 <<1 ),结果影响标志位
SUB SP , #380 ;SP = SP - 380
SBC R0, R1, R2 ;R0 = R1 - R2 - !C
RSC R0, R1, R2 ;R0 = R2 - R1 - !C
(5)乘法指令与乘加指令
ARM微处理器支持的乘法指令与乘加指令共有6条,可分为运算结果为32位和结果为64位两类,与前面的数据处理指令不同,指令中的所有操作数、目的寄存器必须为通用寄存器,不能对操作数使用立即数或被移位的寄存器,同时,目的寄存器和操作数1必须是不同的寄存器。
l MUL指令
格式:
MUL {条件} {S} 目的寄存器,操作数1, 操作数2
功能:
目的寄存器 = 操作数1 × 操作数2,同时可以根据运算结果设置CPSR中相应的条件标志位N和Z。操作数1和操作数2均为32位的有符号数或无符号数。
l MLA指令
格式:
MLA {条件} {S} 目的寄存器,操作数1, 操作数2, 操作数3
功能:
目的寄存器 = 操作数1 × 操作数2 + 操作数3,同时可以根据运算结果设置CPSR中相应的条件标志位N和Z。操作数1和操作数2均为32位的有符号数或无符号数。
l SMULL指令(S:Signed, 有符号)
格式:
SMULL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1, 操作数2
功能:
目的寄存器Low = (操作数1 × 操作数2 )的低32位,
目的寄存器High = (操作数1 × 操作数2 )的高32位,同时可以根据运算结果设置CPSR中相应的条件标志位。操作数1和操作数2均为32位的有符号数。
例:
SMULL R0, R1, R2, R3
;R0 = (R2 ×R3)的低32位, R1 = (R2 ×R3)的高32位。
l SMLAL指令(S:Signed, 有符号)
格式:
SMLAL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1, 操作数2
功能:
目的寄存器Low = (操作数1 × 操作数2 )的低32位 + 目的寄存器Low,
目的寄存器High = (操作数1 × 操作数2 )的高32位 + 目的寄存器High,同时可以根据运算结果设置CPSR中相应的条件标志位。操作数1和操作数2均为32位的有符号数。
例:
SMLAL R0, R1, R2, R3
;R0 = (R2 ×R3)的低32位 + R0,
;R1 = (R2 ×R3)的高32位 + R1。
l UMULL指令(U:UnSigned, 无符号)
格式:
UMULL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1, 操作数2
功能:
目的寄存器Low = (操作数1 × 操作数2 )的低32位,
目的寄存器High = (操作数1 × 操作数2 )的高32位,同时可以根据运算结果设置CPSR中相应的条件标志位。操作数1和操作数2均为32位的无符号数。
例:
UMULL R0, R1, R2, R3
;R0 = (R2 ×R3)的低32位, R1 = (R2 ×R3)的高32位。
l UMLAL指令(U:UnSigned, 无符号)
格式:
UMLAL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1, 操作数2
功能:
目的寄存器Low = (操作数1 × 操作数2 )的低32位 + 目的寄存器Low,
目的寄存器High = (操作数1 × 操作数2 )的高32位 + 目的寄存器High,同时可以根据运算结果设置CPSR中相应的条件标志位。操作数1和操作数2均为32位的无符号数。
例:
UMLAL R0, R1, R2, R3
;R0 = (R2 ×R3)的低32位 + R0,
;R1 = (R2 ×R3)的高32位 + R1。
3. 程序状态寄存器访问指令
功能:用于在程序状态寄存器和通用寄存器之间传送数据。
l MRS指令
格式:
MRS {条件} 通用寄存器, 程序状态寄存器(CPSR、SPSR)
功能:
将状态寄存器的内容传送到通用寄存器。
使用环境:
(1)当需要改变程序状态寄存器的内容时,可用MRS将状态寄存器的内容读入到通用寄存器,修改后再写回到程序状态寄存器。
(2)当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
l MSR指令
格式:
MSR {条件} 程序状态寄存器(CPSR、SPSR)_<域>,操作数
功能:
将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器分为4个域:
F域:位31~位24为条件标志位域;
S域:位23~位16为状态位域;
X域:位15~位8为扩展位域;
C域:位7~位0为控制位域;
使用环境:
(1)当需要改变程序状态寄存器的内容时,可用MRS将状态寄存器的内容读入到通用寄存器,修改后再写回到程序状态寄存器。
(2)当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
例:
MSR CPSR , R0 ;CPSR←R0
MSR SPSR_c , R0 ;传送R0到SPSR,但仅修改SPSR中的控制位域
MSR CPSR_c , #0XD3 ; CPSR[7..0] = 0XD3 , 即切换到管理模式
MSR CPSR_cxsf , R3 ; CPSR ← R3
注意:只有在特权模式下,才能修改状态寄存器。
程序中不能通过MSR指令直接修改CPSR中的T控制位来实现ARM/Thumb状态的切换,必须使用BX指令来完成处理器状态的切换。
MRS与MSR配合使用,可以实现CPSR或SPSR寄存器的读/修改/写操作,进行处理器模式 切换,进行允许/禁止IRQ/FIQ中断等的设置。
例:使能IRQ中断
MRS R0 , CPSR
BIC R0 , R0 , #0X80
MSR CPSR_c , R0
MOV PC , LR
例:禁止IRQ中断
MRS R0 , CPSR
ORR R0 , R0 , #0X80
MSR CPSR_c , R0
MOV PC , LR
4. 存储器加载/存储指令
功能:用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则将寄存器中的数据传送到存储器。
存储器加载/存储指令分为单个存储器加载/存储指令和多个存储器加载/存储指令。
(1)单个存储器加载/存储指令
LDR字数据加载指令;
LDRH(Half)半字数据加载指令;
LDRB字节数据加载指令;
STR字数据存储指令;
STRH半字数据存储指令;
STRB字节数据存储指令。
l 加载指令
格式:
加载指令 {条件} 目的寄存器, <存储器地址>
例:
LDR R0 , [R1] ;将地址为R1的字数据读入R0。
LDR R0 , [R1,R2] ;将地址为R1+R2的字数据读入R0。
LDR R0 , [R1, #4] ;将地址为R1+4的字数据读入R0。
LDR R0 , [R1, R2]! ;将地址为R1+R2的字数据读入R0,并将新地址R1+R2写入R1。
LDR R0 , [R1, R2, LSL #2 ] !
;将地址为R1 + R2 × 4的字数据读入R0,并将新地址R1 + R2 × 4写入R1。
LDRH指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。
LDRH R0 , [R1] ;将地址为R1的半字数据读入R0,并将R0的高16位清零。
LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。
LDRB R0 , [R1] ;将地址为R1的字节数据读入R0,并将R0的高24位清零。
注意:当是字操作时,操作数的地址必须是字对齐的,如果是半字操作,操作数的地址必须是半字对齐。否则,读出的数据是无效,随机的。
例:
LDR R0 , [R1 , # 2 ]
LDRH R0 , [R1 , # 1 ]
l 存储指令
格式:
存储指令 {条件} 源寄存器, <存储器地址>
例:
STR R0 , [R1] , #8
;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0 , [R1 , #8] ;将R0中的字数据写入以R1+8为地址的存储器中。
STRH指令用于从源寄存器中将一个16位的半字数据传送到存储器中。该半字数据为源寄存器中的低16位。
STRH R0 , [R1, #8] ;将寄存器R0中的低16位写入以R1 + 8为地址的存储器中。
STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。
注意:当是字操作时,操作的地址必须是字对齐的,如果是半字操作,操作的地址必须是半字对齐。否则,读出的数据是无效,随机的。
(2)批量数据加载/存储指令
功能:可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令完成相反的操作。
LDM(或STM)指令(Load Data To Multiple Register)
格式:
LDM(或STM) {条件} {类型} 基址寄存器{!} ,寄存器列表{^}
{类型}为以下几种情况:
类 型 |
含 义 |
IA |
每次操作后,地址加4 |
IB |
每次操作前,地址加4 |
DA |
每次操作后,地址减4 |
DB |
每次操作前,地址减4 |
FD |
满递减堆栈 |
ED |
空递减堆栈 |
FA |
满递增堆栈 |
EA |
空递增堆栈 |
{!}为可选后缀,若选用,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0 ~ R15的任意组合。
{^}为可选后缀,当指令为LDM且寄存器列表中包含有R15,选用该后缀表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或付传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
例:
STMFD SP!, {R0 - R7 , LR} ;现场保存,将R0 - R7 , LR入栈,满递减堆栈。
LDMFD SP!, {R0 - R7 , PC}^ ;恢复现场,异常处理返回,满递减堆栈
在进行数据复制时,先设置好源数据指针,然后使用块拷贝寻址指令进行读取和存储。而在堆栈操作中,则要先设置堆栈指针SP,然后使用堆栈寻址指令实现堆栈操作。
5. 数据交换指令
功能:支持在存储器和寄存器之间交换数据。
SWP ( Swap ) 字数据交换指令;
SWPB 字节数据交换指令。
格式:
交换指令 {条件} 目的寄存器, 源寄存器1, [源寄存器2]
例:
SWP R0, R1, [R2] ;将R2所指的存储器中的字数据传送到R0,同时将R1中的字数据传送到R2所指的存储器单元。
显然,当源寄存器1与目的寄存器是同一个寄存器时,就完成了寄存器与存储器间的交换操作。
SWPB指令用于将源寄存器2所指向的存储器中的字节数据到目的寄存器中,目的寄存器的高24位清零,同时将源寄存器1中的低8位数据(低位字节)传送到源寄存器2所指向的存储器中。
6. 异常产生指令
异常指令有两条:SWI软件中断指令和BKPT断点中断指令。
l SWI中断指令
格式:
SWI {条件} 24位的立即数
功能:
产生软件中断,方便用户程序调用操作系统的系统例程。
操作:
切换运行模式到管理模式,设置PC来执行在地址0X08处的下一条指令,设置相应的R13_svc和R14_svc。该指令的操作与执行BL 0X08这条指令的效果是相同的。不同的地方在于,SWI还带有指明系统例程的类型的“24位的立即数”。在具体应用中,为便于记忆,可以使用字符串代替“24位的立即数”,例如:SWI “OS_Write0”和 SWI 0X02是一样的。当指令中24位的立即数被忽略时,系统例程的类型由通用寄存器R0的内容决定。传送给系统例程的参数通过通用寄存器来传递。
l BKPT指令
格式:
BKPT 16位的立即数
功能:
用于产生软件断点中断,执行时中断正常指令,进入相应的调试子程序。
7. 协处理器指令
ARM处理器可支持多达16个协处理器,每个协处理器只执行针对其自身的协处理指令。ARM的协处理器指令主要用于ARM处理器初始化、协处理器的数据处理操作、在ARM处理器与协处理器的寄存器之间传送数据、在协处理器和存储器之间传送数据。ARM协处理器指令有以下5条:
l CDP协处理器数据操作指令;
l LDC协处理器数据加载指令;
l STC协处理器数据存储指令;
l MCR ARM处理器寄存器到协处理器寄存器的数据传送指令;
l MRC 协处理器寄存器到ARM处理器寄存器的数据传送指令。
(1)CDP指令
格式:
CDP {条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2
功能:用于ARM处理器通知协处理器执行特定的操作,若协处理器不能执行指定的操作,则产生未定义指令异常。
注意:指令中涉及到的寄存器都是协处理器的寄存器,不涉及ARM处理器的寄存器和存储器。操作码1、操作码2是协处理器要执行的操作。
例: CDP p5 , 1 , c3 , c4 , c5 , 2
;指示协处理器P5,执行操作1,可选操作为2;C3, C4, C5是相应的协处理器寄存器。
(2) LDC指令
格式:
LDC {条件}{L}协处理器编码,目的寄存器,[源寄存器]
功能:
用于将源寄存器所指向的存储器中的字数据传送到目的寄存器中。若协处理器不能成功执行,则产生未定义指令异常。选项{L}表示指令为长读取操作,可用于双精度数据的传输。
注意:指令中涉及到的源寄存器是ARM处理器的寄存器。
例:LDC P3, C4, [R2, #4]
(3) STC指令
格式:
STC {条件}{L}协处理器编码,源寄存器,[目的寄存器]
功能:
用于将源寄存器中的字数据传送到目的寄存器所指向的存储器中。若协处理器不能成功执行,则产生未定义指令异常。选项{L}表示指令为长读取操作,可用于双精度数据的传输。
注意:指令中涉及到的目的寄存器是ARM处理器的寄存器。
例:STC P3, C4, [R0]
;将协处理器P3的寄存器C4中的数据传送到ARM处理器的寄存器R0所指向的存储器.
(4) MCR指令
格式:
MCR {条件}协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2
功能:
MCR指令用于将ARM处理器寄存器中的数据传送到协处理器的寄存器中。若协处理器不能完成这个操作,将引发未定义指令异常。源寄存器为ARM处理器的寄存器。
(5) MRC指令
格式:
MRC {条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2
功能:
MRC指令用于将协处理器寄存器中的数送到ARM处理器的寄存器中。若协处理器不能完成这个操作,将引发未定义指令异常。源寄存器为ARM处理器的寄存器。
例:MRC P3 , 3 , R0 , C4 , C5 , 6
;将协处理器P3的寄存器C4与C5中的数据传送到ARM的寄存器中,并执行编号为3和6的操作。
Thumb指令集合
Thumb指令集是ARM指令集的一个子集,允许指令编码为16位的长度,Thumb指令集在保留32位代码优势的同时,大大节省了系统的存储空间。
当处理器在执行ARM程序段时,称ARM处理器处于ARM工作状态,当处理器在执行Thumb程序段时,称ARM处理器处于Thumb工作状态。
在编写Thumb指令时,先要用伪指令CODE16声明以下为Thumb指令代码,在ARM指令代码中可以使用BX指令跳转到Thumb指令代码片。同样编写ARM代码时,则使用伪指令CODE32进行声明,在Thumb指令代码中使用BX指令可以跳转到ARM指令代码处。
大多数Thumb指令是无条件执行的,而几乎所有的ARM指令都是有条件执行的。由于Thumb数据处理指令中的目的寄存器与其中的一个源寄存器相同,Thumb指令在指令编码时由三个操作数改为两个操作数。
通常实现同样的程序功能时,所需的Thumb指令的条数比ARM指令多。但使用Thumb指令集合的代码有以下特点:
l 比ARM代码更节略存储空间。
l 使用的指令条数比ARM代码多。
l 若使用32位的存储器,ARM代码比Thumb代码快约40%。
l 若使用16位的存储器,Thumb代码比ARM代码快约40%~50%。
l 与ARM代码相比较,使用Thumb代码,存储器的功耗会降低约30%。
<其它寻址模式与其它指令>
现在我们已经掌握了所有知识,可以编写简单的ARM汇编程序,但如果要编写较为复杂的ARM程序,就必须掌握更多的寻址模式和指令,这就是本文的重点所在。
我们在“基本寻址模式与基本指令”一文中学习了最常用的3种寻址方式。下面介绍其它寻址方式。
1、基址寻址
基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。基址寻址用于访问基址附近的存储单元,常用于查表、数组操作、功能部件寄存器访问等。基址寻址指令举例如下:
LDR R1,[R2,#0x0C]
R2的值+0x0C形成内存地址,读取内存中该地址上的内容,放入R1
其它额外需要了解的内容:
§零偏移。 如:LDR R0,[R1]
§前索引偏移。 如:LDR R0,[R1,#0x04]!,表示将R1的值加上4后作为内存地址,并且指令执行结束时,R1本身的值也要加4。这里!表示要回写
§程序相对偏移。 如:LDR R0,labe1,表示将标号label所代表的地址处存放的内容放入R0,相当于LDR R0, [PC, #某个常数]
§后索引偏移。 如:LDR R0,[R1],#0x04,表示将R1的值作为内存地址,并且指令执行结束时,R1本身的值要加4
2、多寄存器寻址
多寄存器寻址一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器。多寄存器寻址指令举例如下:
LDMIA R1!,{R2-R4,R6} ,它是ldr的多寄存版本,将内存中的4个字放入寄存器R2,R3,R4,R6中
指令执行前
指令执行后
两点说明:
1)、R1!中的!号表示在指令执行完成后,要改变(回写)基址寄存器(R1)的值
2)、寄存器列表{R2-R4, R6}中的顺序并不要紧。最终寄存器与内存地址的对应关系是:编号小的寄存器与内存的低地址相对应
两点问题:
1)、为什么内存起地址是0x40000000,而不是0x40000004
2)、为什么内存地址是从0x40000000 ---- 0x4000000C,而不是从0x3FFFFFF4 ---- 0x40000000
要解释上面2个问题,其实也很简单。其实多寄存加载指令ldm总共有4个:ldmia、 ldmib、 ldmda、 ldmdb。ia的意思是increase after,ib的意思是increase before,da的意思是decrease after,db的意思是decrease before。以LDMIA R1!, {R2-R4, R6}为例子,这里的ia是指办事(将内存中的数加载到寄存器)之后增加基址寄存器(R1)的值。这条指令的执行过程从逻辑上看,如下:
1)、先办事:将R1的值(0x40000000)作为内存地址,到该地址处取得数(0x01),加载到寄存器R2中
2)、后增加:将R1的值从0x40000000增加为0x40000004
再重复上面的操作3次,分别将内存中的数0x02、0x03、0x04放到寄存器中R3、R4、R6中,最后R1的值变为0x40000010。
这个例子中,如果将ldmia改为ldmib,则R2、R3、R4、R6中存放的是0x02、0x03、0x04、内存0x40000010处的内容,最后R1的值为0x40000010。
除了4条多寄存器加载指令外,还有4条类似的多寄存器存储指令,分别是stria、 strib、 strda、 strdb
3、堆栈寻址
由于ARM指令集没有专门的出栈和入栈指令,所以ARM汇编程序是采用SP作为栈指针,以stm指令完成入栈操作,以ldm指令完成出栈操作。
以入栈后SP的值是增加还是减少为依据,可将堆栈类型划分为递增堆栈(向上生长)和递减堆栈(向下生长);
以SP所指向的内存处存放的是栈顶元素还是下一次要入栈的元素,可将堆栈类型划分为满堆栈和空堆栈
那么当堆栈类型为空递减堆栈时候,入栈操作应该使用什么指令?出栈操作应该使用什么指令?进一步,如果堆栈类型为空递增、满递增、满递减堆栈,又将如何呢?如果你不看下面的答案,我相信你一定会让这几个问题折磨得做很多的脑力体操,然后感叹ARM指令集的设计者太不为你这样的程序员考虑了,给了你本不应该由你承担的负荷。但事实上正相反,ARM指令集的设计者充分理解了你作为程序员的苦恼,请看下面的答案。
数据块传送 | 堆栈操作 | 说明 |
存储 | 压栈 | |
STMDA | STMED | 空递减 |
STMIA | STMEA | 空递增 |
STMDB | STMFD | 满递减 |
STMIB | STMFA | 满递增 |
数据块传送 | 堆栈操作 | 说明 |
加载 | 出栈 | |
LDMDA | LDMFA | 满递增 |
LDMIA | LDMFD | 满递减 |
LDMDB | LDMEA | 空递增 |
LDMIB | LDMED | 空递减 |
这2张表的第一、三列回答了前面你绞尽脑汁回答的问题。而第二列则体现了ARM指令集的设计者对作为程序员的你的充分体贴。第二列中的ED、EA、FD、FA分别表示empty descend(空递减)、 empty ascend(空递增)、 full descend(满递减)、 full ascend(满递增),其含义是说,如果你采用的是空递减(空递增、满递减、满递增)堆栈的话,入栈操作则使用指令STMED(STMEA、STMFD、STMFA),出栈操作则使用指令LDMED(LDMEA、LDMFD、LDMFA)。从此你再也不会为你应该使用ia、ib、da还是db来实现出、入栈操作而苦恼了。
STMED、STMEA、STMFD、STMFA和LDMED、LDMEA、LDMFD、LDMFA就是所谓的堆栈寻址指令。由此可见:为了对程序员体贴入微,ARM指令集的设计者设计了堆栈寻址指令,其实质就是多寄存寻址指令的快捷方式。
4、寄存器移位寻址
寄存器移位寻址是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,选择进行移位操作。例如:
MOV R0,R2,LSL #3 表示将R2的值逻辑左移3位,结果放入R0,即是R0=R2×8。
移位的方式有以下几种:
LSL(logic shift left):逻辑左移
LSR(logic shift right):逻辑右移
ASR(arithmetic shift right):算术右移
ROR(rotate shift right):循环右移
RRX(rotate shift right with extend):带扩展的循环右移。其中的C指的是CPSR的C位
5、相对寻址
相对寻址是基址寻址的一种变通。由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。例如:
B LOOP
...
LOOP MOV R6,#1
该条B指令的意思是要跳转到标号LOOP所代表的指令处,其含义相当明显,但你要明白CPU根本不明白标号是个什么东西(事实上在指令的机器码中根本就没有标号这种东西),那么b loop这条指令的机器码会是什么呢?答案是:高8bit是操作码相关内容,低24bit是一个常数,表示从b指令到mov指令之间的内存地址的差值(如果不考虑流水线的影响的话)。由此可见,b loop这条指令相当于add pc, pc, #偏移量常数,典型的相对于PC(当前指令地址)的相对寻址。由于是相对于当前指令地址进行相对寻址,所以无论程序最终运行在内存的何处(即使运行的地址不是它预期的位置),这条B指令都能正确运行。关于相对寻址、程序期望的运行地址等等,我将在“ARM汇编伪指令”一文中详细描述。
随便说一下,前面学到b指令的跳转范围是当前指令的先后32M,为什么是这个范围呢?因为24bit常数用1个比特区别正负,还剩23bit,同时由于ARM指令在内存中的地址其最低2bit一定是0(为什么?请自行思考一下),因此23bit中可以不必表示这2个0,所以23bit可以表示的范围是0 ---- 2^25,即:0 ---- 32M。
我们在“基本寻址模式与基本指令”一文中学习了最常用的指令。下面介绍其它较为常用的指令。
1、访存指令
LDRH(半字加载);LDRSH (有符号半字加载);STRH(半字存储)
交换指令
助记符 |
说明 |
操作 |
SWP Rd,Rm,[Rn] |
寄存器和存储器字数据交换 |
Rd←[Rn],[Rn]←Rm (Rn≠Rd或Rm) |
SWPB Rd,Rm,[Rn] |
寄存器和存储器字节数据交换 |
Rd←[Rn],[Rn]←Rm (Rn≠Rd或Rm) |
2、数据处理指令
助记符 |
说明 |
操作 |
MVN Rd,operand2 |
数据非传送 |
Rd←(~operand2) |
助记符 |
说明 |
操作 |
RSB Rd, Rn, operand2 |
逆向减法指令 |
Rd←operand2-Rn |
ADC Rd, Rn, operand2 |
带进位加法 |
Rd←Rn+operand2+Carry |
SBC Rd, Rn, operand2 |
带进位减法指令 |
Rd←Rn-operand2-(NOT)Carry |
RSC Rd, Rn, operand2 |
带进位逆向减法指令 |
Rd←operand2-Rn-(NOT)Carry |
这里要特别提到,ADC指令结合CPSR,可以实现64位整数加法,详情参见“杂项解释”一文
助记符 |
说明 |
操作 |
BIC Rd, Rn, operand2 |
按位清除指令 |
Rd←Rn & (~operand2) |
其实现功能是:将Rn中对应于operand2中为1的bit位全部清0,然后将结果保存到Rd中
助记符 |
说明 |
操作 |
CMN Rn, operand2 |
负数比较指令 |
标志N、Z、C、V←Rn+operand2 |
TST Rn, operand2 |
位测试指令 |
标志N、Z、C←Rn & operand2 |
TEQ Rn, operand2 |
相等测试指令 |
标志N、Z、C←Rn ^ operand2 |
TST指令测试的是:Rn中所有指定bit位是否全为0(指定的bit位是operand2中为1的所有位);
TEQ指令测试的是:Rn和operand2是否相等。这点上与CMP指令一样,区别在于CMP指令除了可以比较2个数是否相等外,也可以比较2个数谁大谁小,但TEQ不行。
3、乘法指令
助记符 |
说明 |
操作 |
MUL Rd,Rm,Rs |
32位乘法指令 |
Rd←Rm*Rs (Rd≠Rm) |
MLA Rd,Rm,Rs,Rn |
32位乘加指令 |
Rd←Rm*Rs+Rn (Rd≠Rm) |
UMULL RdLo,RdHi,Rm,Rs |
64位无符号乘法指令 |
(RdLo,RdHi) ←Rm*Rs |
UMLAL RdLo,RdHi,Rm,Rs |
64位无符号乘加指令 |
(RdLo,RdHi) ←Rm*Rs+(RdLo,RdHi) |
SMULL RdLo,RdHi,Rm,Rs |
64位有符号乘法指令 |
(RdLo,RdHi) ←Rm*Rs |
SMLAL RdLo,RdHi,Rm,Rs |
64位有符号乘加指令 |
(RdLo,RdHi) ←Rm*Rs+(RdLo,RdHi) |
4、协处理器指令
参见“MMU与内存保护的实现”一文
5、杂项指令
SWI:软中断指令,参见“swi与system call的实现”一文
MRS、MSR:程序状态寄存器操作指令,参见“ARM异常处理”一文
以上摘自互联网
另外附条bcc(转移)公式,自己捉摸的, 可能有不对的地方.
计算opcode:
(dst - src) / 4 - 2 = opcode
计算dst:
src + (opcode + 2) * 4 = dst
不明看以下示例:)
1 ROM:9D039D3C 108 1C 00 00 1A BNE loc_9D039DB4 ; Branch 2 (9D039DB4 - 9D039D3C) / 4 - 2 = 1c 3 9D039D3C + (1c + 2) * 4 4 5 6 7 ROM:9D039D90 108 07 00 00 EA B loc_9D039DB4 ; Branch 8 (9D039DB4 - 9D039D90) / 4 - 2 = 7 9 9D039D90 + (7 + 2) * 4