文章目录
-
- 前情提要
- 第三章 ARM 指令集
-
- arm指令概述
- 1. 寻址方式的概念,及 9 种寻址方式(选择题,那种寻址方式)
- 2. ARM 指令集,掌握常用的指令、指令格式、第 2 个操作数(立即数、寄存器、寄存器移位)
- 3. Thumb 指令(不考)
前情提要
本人是一名大三学生,由于期末复习需要,所以按照老师的ppt总结整理此笔记,希望对你有所帮助
第三章 ARM 指令集
arm指令概述
ARM微处理器是基于精简指令计算机(RISC)的原理设计的,指令集和相关译码机制比较简单。
ARM7系列微处理器具有32位的ARM指令集和16位的Thumb指令集:
ARM指令集效率高,但是代码密度低,占用较大的内存空间;
Thumb指令集属于ARM指令集的子集,具有较好的代码密度,功能简单。
所有的ARM指令都是可以有条件执行
而Thumb指令集只有一条指令(B)具有条件执行的功能。
ARM指令和Thumb指令可以相互调用,两者之间的状态切换所用的开销几乎为0。
1. 寻址方式的概念,及 9 种寻址方式(选择题,那种寻址方式)
寻址方式:处理器根据指令中给出的地址信息来寻找被操作对象的物理地址的方式。
ARM处理器具有9种基本寻址方式。
寄存器寻址
操作数的值在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器值来操作。
寄存器寻址指令举例如下:MOV R1,R2 ;将R2的值存入R1 SUB R0,R1,R2 ;将R1的值减去R2的值,结果保存到R0
立即寻址
立即寻址指令中的操作码字段后面的地址码部分即是操作数本身,也就是说,数据就包含在指令当中,取出指令也就取出了可以立即使用的操作数(这样的数称为立即数)。
立即寻址指令举例如下:
SUBS R0,R0,#1 ;R0减1,结果放入R0,并且影响标志位 MOV R0,#0xFF000 ;将立即数0xFF000装入R0寄存器
寄存器移位寻址
以寄存器寻址为本,将寄存器中的数移位后作为操作数。
寄存器移位寻址是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,选择进行移位操作。
寄存器移位寻址指令举例如下:
MOV R0,R2,LSL #3 ;R2的值左移3位,结果放入R0, ;即是R0=R2×8 ANDS R1,R1,R2,LSL R3 ;R2的值左移R3位,然后和R1相 ;“与”操作,结果放入R1
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的值来填充,低端移出的位填入进位标志位。
寄存器间接寻址
寄存器间接寻址指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存在寄存器指定地址的存储单元中,即寄存器为操作数的地址指针。
寄存器间接寻址指令举例如下:
LDR R1,[R2] ;将R2指向的存储单元的数据读出 ;保存在R1中 STR R1,[R0]
基址寻址
基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。
- LDR:加载指定位置32位数据到目标寄存器;(从固定地址处进行取值)
- STR:保存指定位置32位数据到目标位置;(给固定位置进行赋值)
- 与mov的区别 mov只能在寄存器之间或立即数与寄存器之间传输数据
基址寻址指令举例如下:
LDR R2,[R3,#0x0C] ;读取R3+0x0C地址上的存储单元的内容,放入R2 STR R1,[R0,#-4]! ;先R0-4,然后把R1的值保存到R0-4指定的存储单元
多寄存器寻址
多寄存器寻址一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器。
多寄存器寻址指令举例如下:LDMIA R1!,{R2-R7,R12} ;将R1指向的单元中的数据读出到 ;R2~R7、R12中(R1自动加4) STMIA R0!,{R2-R7,R12} ;将寄存器R2~R7、R12的值保 ;存到R0指向的存储; 单元中 ;(R0自动加4)
使用多寄存器寻址指令时,寄存器子集的顺序是按由小到大的顺序排列,连续的寄存器可用“-”连接;否则用“,”分隔书写。
堆栈寻址
堆栈即Stack,因为CPU的寄存器总是及其有限的,很多时候我们不得不使用内存来存储数据,比如进行多级跳转的时候,这时候堆栈就是一个很好的工具,每次跳转就将当前函数的返回地址存储到内存,最底层被调用的子函数会最先返回,就先将压入栈的现场返回,以此类推…,ARM使用SP(R13)作为栈指针,ARM设计的内存栈模型有2×2=4种
按照栈在内存增长的方向分为递增栈和递减栈 :
递增(Increase) 堆栈:向堆栈写入数据时,堆栈由低地址向高地址生长。
递减(Descend) 堆栈:向堆栈写入数据时,堆栈由高地址向低地址生长。
根据堆栈指针SP指向的位置,又可以把堆栈分为满堆栈和空堆栈两种。
满堆栈(Full Stack):SP始终指向栈顶元素,压栈的时候先移动SP,再将数据放入SP指向的地址。
空堆栈(Empty Stack):SP始终指向下一个将要放入元素的位置,压栈时先将数据放入SP指向的地址,再移动SP
F: full
E:empty
A:Ascending
D:Descending
LDM:(load much)多数据加载,将地址上的值加载到寄存器上
STM:(store much)多数据存储,将寄存器的值存到地址上
最后,可以得到4种基本的堆栈类型:
- 满递增:堆栈向上增长,堆栈指针指向含有效数据项的最高地址。指令如LDMFA、STMFA等;
- 空递增:堆栈向上增长,堆栈指针指向堆栈上的第一个空位置。指令如LDMEA、STMEA等;
- 满递减:堆栈向下增长,堆栈指针指向内含有效数据项的最低地址。指令如LDMFD、STMFD等;
- 空递减:堆栈向下增长,堆栈指针向堆栈下的第一个空位置。指令如LDMED、STMED等。
stmfd sp!, {r1-r7, lr} ;将r1到r7和lr的数据压入fd栈
块拷贝寻址
IB:Increment Before Operating
IA:Increment After Operating
DB:Decrement Before Operating
DA:Decrement After Operating
块拷贝寻址方式使用多寄存器传送指令将数据块从存储器的某一位置拷贝到另一位置。 如:
STMIA R0!,{R1-R7} ;将R1~R7的数据保存到存储器中。 ;存储指针在保存第一个值之后增加, ;增长方向为向上增长。 STMIB R0!,{R1-R7} ;将R1~R7的数据保存到存储器中。 ;存储指针在保存第一个值之前增加, ;增长方向为向上增长。
相对寻址
[!tip]
相对寻址是基址寻址的一种变通。
- 由程序计数器PC提供基准地址,
- 指令中的地址码字段作为偏移量,
- 两者相加后得到的地址即为操作数的有效地址
;例子 BL SUBRl ;调用到SUBRl子程序 . . . SUBR1: . . . MOV PC,R14 ;返回
2. ARM 指令集,掌握常用的指令、指令格式、第 2 个操作数(立即数、寄存器、寄存器移位)
指令格式
<opcode> {<cond>} {S} <Rd> ,<Rn>{,<operand2>}
其中<>号内的项是必须的,{ }号内的项是可选的。各项的说明如下:
opcode:指令助记符;
cond:执行条件;
[!IMPORTANT]
使用条件码“cond”可以实现高效的逻辑操作,提高代码效率。
所有的ARM指令都可以条件执行,
而Thumb指令只有B(跳转)指令具有条件执行 功能。如果指令不标明条件代码,将默认为无条件(AL)执行。
指令条件码表
-
操作码 条件助记符 标志 含义 0000 EQ Z=1 相等 0001 NE Z=0 不相等 0010 CS/HS C=1 无符号数大于或等于 0011 CC/LO C=0 无符号数小于 0100 MI N=1 负数 0101 PL N=0 正数或零 0110 VS V=1 溢出 0111 VC V=0 没有溢出 1000 HI C=1,Z=0 无符号数大于 1001 LS C=0,Z=1 无符号数小于或等于 1010 GE N=V 有符号数大于或等于 1011 LT N!=V 有符号数小于 1100 GT Z=0,N=V 有符号数大于 1101 LE Z=1,N!=V 有符号数小于或等于 1110 AL 任何 无条件执行 (指令默认条件) 1111 NV 任何 从不执行(不要使用)
S:是否影响CPSR寄存器的值;
Rd:目标寄存器;
Rn:第1个操作数的寄存器;
operand2:第2个操作数;
[!tip]
灵活地使用第2个操作数“operand2”能够提高代码效率。
operand2”有如下的形式:- #immed_8r ——常数表达式;
;【例】 MOV R0,#1 ;R0 ←1 AND R1,R2,#0x0F ;R2与0x0F,结果保存在R1 LDR R0,[R1],#-4 ;读取R1地址上的存储器单元内容, ;赋值给R0, ;传送完成后 R1← R1-4,后变址
- Rm ——寄存器方式;
;【例】 在寄存器方式下,操作数即为寄存器的数值。 SUB R1,R1,R2 ; R1-R2→R1 LDR R0,[R1],-R2 ;先将R1指向内存中的数存入R0 ; 之后,R1 ← R1- R2 ; 后变址的方式
- Rm, shift ——寄存器移位方式;
将寄存器的移位结果作为操作数,但Rm值保持不变,移位方法如下:
操作码 说明 操作码 说明 ASR #n 算术右移n位 ROR #n 循环右移n位 LSL #n 逻辑左移n位 RRX 带扩展的循环右移1位 LSR #n 逻辑右移n位 - 算术左移与逻辑左移是一样的!
;寄存器偏移方式应用举例: ADD R1,R1,R1,LSL #3 ;R1=R1×8 SUB R1,R1,R2,LSR #2 ;R1=R1-R2÷4
;例子
LDR R0,[R1] ;读取R1地址上的存储器单元内容,执行条件AL (无条件)
BEQ D1 ;分支指令,执行条件EQ,即相等则跳转到D1标号
ADDS R1,R1,#1 ;加法指令,R1+1 → R1,影响CPSR
SUBNES R1,R1,#0x10 ;条件执行减法运算(NE),R1 – 0x10 → R1,影响CPSR
条件码(常用的条件码的助记符,如等于,不等于,大于,小于等)
使用条件码“cond”可以实现高效的逻辑操作,提高代码效率。
所有的ARM指令都可以条件执行,
而Thumb指令只有B(跳转)指令具有条件执行 功能。如果指令不标明条件代码,将默认为无条件(AL)执行。
指令条件码表
操作码 | 条件助记符 | 标志 | 含义 | |
---|---|---|---|---|
0000 | EQ | Z=1 | 相等 | |
0001 | NE | Z=0 | 不相等 | |
0010 | CS/HS | C=1 | 无符号数大于或等于 | |
0011 | CC/LO | C=0 | 无符号数小于 | |
0100 | MI | N=1 | 负数 | |
0101 | PL | N=0 | 正数或零 | |
0110 | VS | V=1 | 溢出 | |
0111 | VC | V=0 | 没有溢出 | |
1000 | HI | C=1,Z=0 | 无符号数大于 | |
1001 | LS | C=0,Z=1 | 无符号数小于或等于 | |
1010 | GE | N=V | 有符号数大于或等于 | |
1011 | LT | N!=V | 有符号数小于 | |
1100 | GT | Z=0,N=V | 有符号数大于 | |
1101 | LE | Z=1,N!=V | 有符号数小于或等于 | |
1110 | AL | 任何 | 无条件执行 (指令默认条件) | |
1111 | NV | 任何 | 从不执行(不要使用) |
单寄存器存取指令(LDR,STR)
ARM处理器是典型的RISC处理器,对存储器的访问只能使用加载和存储指令实现。
冯•诺依曼存储结构,程序空间、RAM空间及I/O映射空间统一编址,除对RAM操作以外,对外围IO、程序数据的访问均要通过加载/存储指令进行。
存储器访问指令分为单寄存器操作指令和多寄存器操作指令。
单寄存器操作指令
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
LDR Rd,addressing | 加载字数据 | Rd←[addressing],addressing索引 | LDR{cond} |
LDRB Rd,addressing | 加载无符号字节数据 | Rd←[addressing],addressing索引 | LDR{cond}B |
LDRT Rd,addressing | 以用户模式加载字数据 | Rd←[addressing],addressing索引 | LDR{cond}T |
LDRBT Rd, addressing | 以用户模式加载无符号字节数据 | Rd←[addressing],addressing索引 | LDR{cond}BT |
LDRH Rd, addressing | 加载无符号半字数据 | Rd←[addressing],addressing索引 | LDR{cond}H |
LDRSB Rd, addressing | 加载有符号字节数据 | Rd←[addressing],addressing索引 | LDR{cond}SB |
LDRSH Rd, addressing | 加载有符号半字数据 | Rd←[addressing],addressing索引 | LDR{cond}SH |
[!NOTE]
LDRSB, LDRSH加载后高位需要填充符号位!
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
STR Rd, addressing | 存储字数据 | [addressing]←Rd, addressing索引 | STR{cond} |
STRB Rd,addressing | 存储字节数据 | [addressing]←Rd, addressing索引 | STR{cond}B |
STRT Rd,addressing | 以用户模式存储字数据 | [addressing]←Rd, addressing索引 | STR{cond}T |
STRBT Rd,addressing | 以用户模式存储字节数据 | [addressing]←Rd, addressing索引 | STR{cond}BT |
STRH Rd,addressing | 存储半字数据 | [addressing] ←Rd, addressing索引 | STR{cond}H |
LDR/STR指令用于对内存变量的访问、内存缓冲区数据的访问、外围部件的控制操作等。若使用LDR指令加载数据到PC寄存器,则实现程序跳转功能,这样也就实现了程序跳转。
[!NOTE]
说明:
所有单寄存器加载/存储指令可分为“字和无符号字节加载存储指令”
“半字和有符号字节加载存储指令。
LDR和STR——(1)字和无符号字节加载/存储指令
LDR/STR指令寻址非常灵活,它由两部分组成,其中一部分为一个基址寄存器,可以为任一个通用寄存器;另一部分为一个地址偏移量。地址偏移量有以下3种格式:
**立即数:**立即数可以是一个无符号的数值。这个数据可以加到基址寄存器,也可以从基址寄存器中减去这个数值。
;【例】 LDR R1,[R0,#0x12] ;将R0+0x12地址处的数据读出,保存到Rl中(R0的值不变) LDR R1,[R0,# -0x12] ;将R0-0x12地址处的数据读出,保存到R1中(R0的值不变)
**寄存器:**寄存器中的数值可以加到基址寄存器,也可以从基址寄存器中减去这个数值。
【;例】 LDR R1,[R0,R2] ;将R0+R2地址处的数据读出,保存到R1中 LDR R1,[R0,-R2] ;将R0-R2地址处的数据读出,保存到R1中
**寄存器及移位常数:**寄存器移位后的值可以加到基址寄存器,也可以从基址寄存器中减去这个数值。
;【例】 LDR R1,[R0,R2,LSL #2] ;将R0+R2×4地址处的数据读出,保存到R1中(R0、R2的值不变) LDR R1,[R0,-R2,LSL #2];将R0-R2×4地址处的数据读出,保存到R1中(R0、R2的值不变)
单寄存器存取指令:前变址 vs 后变址
从寻址方式的地址计算方法分,加载/存储指令有以下3种格式:
- 零偏移: 如:LDR Rd,[Rn]
- 前索引偏移(前变址):如:LDR Rd,[Rn,#0x04]!
- 后索引偏移(后变址):如:LDR Rd,[Rn],#0x04
根据数据传输传输的时机以及在指令执行后基址寄存器是否被更新,寄存器变址有前变址、回写前变址和后变址暗中方式。
前变址:
执行指令的时候,如果先进行变址运算,后传递数据,那么这种方式就是前变址方式。如图1所示
例子:
LDR R0,[R1,R2] ;R0<-((R1)+(R2))
LDR R0,[R1,#8] ;R0<-((R1)+8)
后变址
与上面相反,先传输数据,后进行变址的运算的方式叫做后变址方式,从指令格式上来看,后变址指令的格式如下:
LDR R0,[R1],#8 ;R0<-((R1)) R1<-((R1)+#8)
即将偏移量写在了方括号外边。后变址指令的执行过程如图2。
单寄存器存取指令:半字和有符号字节
这类LDR/STR指令可加载有符号半字或字节,可加载/存储无符号半字。偏移量格式、寻址方式与加载/存储字和无符号字节指令相同。
LDR{cond}SB Rd,<地址> ;将指定地址上的有符号字节读入Rd
LDR{cond}SH Rd,<地址> ;将指定地址上的有符号半字读入Rd
LDR{cond}H Rd,<地址> ;将指定地址上的半字数据读入Rd
STR{cond}H Rd,<地址> ;将Rd中的半字数据存入指定地址
[!NOTE]
注意:
1.有符号位半字/字节加载是指用符号位加载扩展到32位,
无符号半字加载是指用零扩展到32位;
2.地址对齐——半字读写的指定地址必须为偶数,否则将产生不可靠的结果。
例子
;【例】
LDRSB R1,[R0,R3] ;将R0+R3地址上的字节数据读到R1,高24位用符号位扩展
LDRSH R1,[R9] ;将R9地址上的半字数据读出到R1,高16位用符号位扩展
LDRH R6,[R2],#2;将R2地址上的半字数据读出到R6,高16位用零扩展,R2=R2+2,后变址方式
STRH R1,[R0,#2]! ;将R1的数据保存到R0+2地址中,只存储低2字节数据,R0=R0+2,前变址方式
多寄存器加载指令(LDM,STM)
LDM和STM指令可以实现在一组寄存器和一块连续的内存单元之间传输数据。
LDM为加载多个寄存器;
STM为存储多个寄存器。
允许一条指令传送16个寄存器的任何子集或所有寄存器。
;指令格式如下:
LDM{cond}<模式> Rn{!},reglist{^}
STM{cond}<模式> Rn{!},reglist{^}
[!tip]
LDM和STM的主要用途是现场保护、数据复制、常数传递等
注意Rn此处没有[ ],但仍然指向内存
指令格式中,寄存器Rn为基址寄存器,装有传送数据的初始地址,Rn不允许为R15。
后缀“!”表示最后的地址写回到Rn中。
寄存器列表reglist可包含多于一个寄存器或包含寄存器范围,使用“,”分开,如{R1,R2,R6-R9},寄存器按由小到大排列。
后缀“^”不允许在用户模式或系统模式下使用。
若在LDM指令且寄存器列表中包含有PC时使用"^",那么除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中,这可用于异常处理返回。
使用后缀“^”进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前异常模式的寄存器。
模式 | 说明 | 模式 | 说明 |
---|---|---|---|
IA | 每次传送后地址加4 | FD | 满递减堆栈 |
IB | 每次传送前地址加4 | ED | 空递减堆栈 |
DA | 每次传送后地址减4 | FA | 满递增堆栈 |
DB | 每次传送前地址减4 | EA | 空递增堆栈 |
数据块传送操作 | 堆栈操作 |
[!important]
**进行数据复制时,**先设置好源数据指针和目标指针,然后使用块拷贝寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB进行读取和存储 。
**进行堆栈操作操作时,**要先设置堆栈指针(SP),然后使用堆栈寻址指令STMFD/LDMFD 、STMED/LDMED、STMFA/LDMFA和STMEA/LDMEA实现堆栈操作。
例子:
;【例】
LDMIA R0!,{R3 - R9};加载R0指向地址上的多字数据,保存到R3~R9中,R0值更新,增量后
STMIA R1!,{R3 - R9};将R3~R9的数据存储到R1指向的地址上,R1值更新,增量后
STMFD SP!,{R0 - R7,LR} ;现场保存,将R0~R7、LR入栈 从右往左压,先压LR,满递减
LDMFD SP!,{R0 - R7,PC} ;恢复现场,异常处理返回 弹出从左往右,先弹出R0,满递减
寄存器和存储器交换指令
SWP指令用于将一个内存单元(该单元地址放在寄存器Rn中)的内容读取到一个寄存器Rd中,同时将另一个寄存器Rm的内容写入到该内存单元中。
指令格式如下:
SWP{cond}{B} Rd,Rm,[Rn]
[!tip]
B为可选后缀,若有B,则交换字节,否则交换32位字;
Rd用于保存从存储器中读入的数据;
Rm的数据用于存储到存储器中,
若Rm与Rd相同,则为寄存器与存储器内容进行交换;
Rn为要进行数据交换的存储器地址,Rn不能与Rd和Rm相同。
例子:
;【例】
SWP R1,R1,[R0] ;将R1的内容与R0指向的存储单元的内容进行交换
SWPB R1,R2,[R0] ;将R0指向的存储单元内的容读取一字节数据到R1中
;(高24位清零),并将R2的内容写入到该内存单元中
;(最低字节有效) ,只写R2的最低8位给内存
数据处理指令(数据传送指令 MOV, MVN,算术逻辑运算指令 ADD, SUB 等,比较指令 CMP, CMN,TST 等)
数据处理指令大致可分为3类:
- 数据传送指令;
- 算术逻辑运算指令;
- 比较指令。
[!warning]
- 数据处理指令只能对寄存器的内容进行操作,而不能对内存中的数据进行操作。
- 所有ARM数据处理指令均可选择使用S后缀,并影响状态标志。
- 比较指令CMP、CMN、TST和TEQ不需要后缀S,它们会直接影响状态标志。
数据传送指令
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
MOV Rd,operand2 | 数据传送 | Rd←operand2 | MOV{cond}{S} |
MVN Rd,operand2 | 数据非传送 | Rd←(~operand2) | MVN{cond}{S} |
MOV指令将立即数或寄存器传送到目标寄存器(Rd),可用于移位运算等操作。指令格式如下:
MOV{cond}{S} Rd,operand2
例子:
;MOV指令举例如下:
MOVS R3,R1,LSL #2 ;R3=R1<<2,并影响NZC标志位
MOV PC,LR ;PC=LR,子程序返回
MOV R4,#0x80 ;R4 0x80
MVN指令将立即数或寄存器(operand2)按位取反后传送到目标寄存器(Rd),因为其具有取反功能,所以可以装载范围更广的立即数。指令格式如下:
MVN{cond}{S} Rd,operand2
;MVN指令举例如下:
MVN R1,#0xFF ;R1← 0xFFFFFF00
MVN R1,R2 ;将R2取反,结果存到R1
MVNS R3,R1,LSL #2 ;R3←(R1<<2)取反并影响NZC标志位
算术运算
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
ADD Rd,Rn,operand2 | 加法运算指令 | Rd←Rn+operand2 | ADD{cond}{S} |
SUB Rd, Rn,operand2 | 减法运算指令 | Rd←Rn-operand2 | SUB{cond}{S} |
RSB Rd, Rn, operand2 | 逆向减法指令 | Rd←operand2-Rn | RSB{cond}{S} |
ADC Rd, Rn,operand2 | 带进位加法 | Rd←Rn+operand2+Carry | ADC{cond}{S} |
SBC Rd, Rn,operand2 | 带进位减法指令 | Rd←Rn-operand2-(NOT)Carry | SBC{cond}{S} |
RSC Rd, Rn,operand2 | 带进位逆向减法指令 | Rd←operand2-Rn-(NOT)Carry | RSC{cond}{S} |
应用示例:
ADDS R1,R1,#1 ;R1←R1+1,并影响标志位
ADDS R3,R1,R2,LSL #2 ; R3←R1+R2<<2
ADDS R3,R1,R2 ; R3←R1+R2
SUBS R0,R0,#1 ;R0←R0-1
SUB R6,R7,#0x10 ; R6←R7-0x10
RSB R3,R1,#0xFF00 ;R3=0xFF00-R1
RSBS R1,R2,R2,LSL #2 ;R1=(R2<<2)-R2=R2×3
ADDS R0,R0,R2 ;使用ADC实现64位加法
ADC R1,R1,R3 ;(R1、R0)=(R1、R0)+(R3、R2)
SUBS R0,R0,R2 ;使用SBC实现64位减法
SBC R1,R1,R3 ; (R1、R0)=(R1、R0)-(R3、R2)
RSBS R2,R0,#0
RSC R3,R1,#0 ;使用RSC指令实现求64位数值的负数
逻辑运算指令
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
AND Rd, Rn, operand2 | 逻辑与操作指令 | Rd←Rn & operand2 | AND{cond}{S} |
ORR Rd, Rn, operand2 | 逻辑或操作指令 | Rd←Rn | operand2 | ORR{cond}{S} |
EOR Rd, Rn, operand2 | 逻辑异或操作指令 | Rd←Rn ^ operand2 | EOR{cond}{S} |
BIC Rd, Rn, operand2 | 位清除指令 | Rd←Rn & (~operand2) | BIC{cond}{S} |
例子:
ANDS R0,R0,#0x01 ;R0←R0&0x01,取出最低位数据
AND R2,R1,R3 ;R2←R1&R3
ORR R0,R0,#0x0F ;将R0的低4位置1,其它4位不变
EOR R1,R1,#0x0F ;将R1的低4位取反,其它位不变
EORS R0,R5,#0x01 ; 将R5和0x01进行逻辑异或,
;结果保存到R0,并影响标志位
BIC R1,R1,#0x0F ;将R1的低4位清零,其它28位不变
比较指令
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
CMP Rn, operand2 | 比较指令 | 标志N、Z、C、V ←Rn-operand2 | CMP{cond} |
CMN Rn, operand2 | 负数比较指令 | 标志N、Z、C、V←Rn+operand2 | CMN{cond} |
TST Rn, operand2 | 位测试指令 | 标志N、Z、C、V←Rn & operand2 | TST{cond} |
TEQ Rn, operand2 | 相等测试指令 | 标志N、Z、C、V←Rn ^ operand2 | TEQ{cond} |
[!tip]
没有目的操作数,只用作更新条件标志位,不保存运算结果,指令后缀无需加S
Z:指令结果为0时Z=1,表示比较结果相等;否则Z=0;
C:指令有借位C=0 否则C=1,有仅为C=1,否则C=1
注意:
- CMP指令与SUBS指令的区别在于CMP指令不保存运算结果。
- CMN指令与ADDS指令的区别在于CMN指令不保存运算结果。
- TST指令与ANDS指令的区别在于TST指令不保存运算结果。
- TST指令通常与EQ、NE条件码配合使用,当所有测试位均为0时,EQ有效,而只要有一个测试位不为0,则NE有效。
- TEQ指令与EORS指令的区别在于TEQ指令不保存运算结果。使用TEQ进行相等测试时,常与EQ、NE条件码配合使用。当两个数据相等时,EQ有效;否则NE有效。
例子
CMP R1,#10 ; R1与10比较,设置相关标志位
CMN R0,#1 ;R0+1,判断相加结果时候为0,若是, 则Z位置1。
TST R0,#0x01 ; 判断R0的最低位是否为0
TST R1,#0x0F ; 判断R1的低4位是否为0
TEQ R0,R1 ; 比较R0与R1是否相等 (不影响V位和C位)
乘法指令(MUL,MLA 等)
ARM7TDMI具有三种乘法指令,分别为:
- 32×32位乘法指令;
- 32× 32位乘加指令;
- 32× 32位结果为64位的乘/乘加指令。
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
MUL Rd,Rm,Rs | 32位乘法指令 | Rd←Rm*Rs (Rd≠Rm) | MUL{cond}{S} |
MLA Rd,Rm,Rs,Rn | 32位乘加指令 | Rd←Rm*Rs+Rn (Rd≠Rm) | MLA{cond}{S} |
UMULL RdLo,RdHi,Rm,Rs | 64位无符号乘法指令 | (RdHi,RdLo) ←Rm*Rs | UMULL{cond}{S} |
UMLAL RdLo,RdHi,Rm,Rs | 64位无符号乘加指令 | (RdHi,RdLo) ←Rm*Rs+(RdHi,RdLo) | UMLAL{cond}{S} |
SMULL RdLo,RdHi,Rm,Rs | 64位有符号乘法指令 | (RdHi,RdLo) ←Rm*Rs | SMULL{cond}{S} |
SMLAL RdLo,RdHi,Rm,Rs | 64位有符号乘加指令 | (RdHi,RdLo) ←Rm*Rs+(RdHi,RdLo) | SMLAL{cond}{S} |
[!tip]
- 32位乘法指令,乘法操作的结果为32位
- 64位乘法指令,乘法操作的结果为64位
- S决定指令的操作是否影响CPSR的N和Z位的值
应用示例
MUL R1,R2,R3 ;R1=R2×R3
MULS R0,R3,R7 ;R0=R3×R7,同时影响CPSR中的N位和Z位
MLA R1,R2,R3,R0 ; R1=R2×R3+R0
UMULL R0,R1,R5,R8 ; (R1、R0)=R5×R8
UMLAL R0,R1,R5,R8 ;(R1、R0)=R5×R8+(R1、R0)
SMULL R2,R3,R7,R6 ; (R3、R2)=R7×R6
SMLAL R2,R3,R7,R6 ; (R3、R2)=R7×R6+(R3、R2)
分支指令(B,BL,BX)
[!tip]
在ARM中有两种方式可以实现程序的跳转:
一种是使用分支指令直接跳转,向前或向后32MB
另一种则是直接向PC寄存器赋值实现跳转,4GB
分支指令有以下三种:
- 分支指令B;
- 带链接的分支指令BL;
- 带状态切换的分支指令BX。
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
B label | 分支指令 | PC←label | B{cond} |
BL label | 带链接的分支指令 | LR←PC-4,PC←label | BL{cond} |
BX Rm | 带状态切换的分支指令 | PC←label,切换处理器状态 | BX{cond} |
[!important]
带链接的分支指令——BL指令适用于子程序调用,使用该指令后,**下一条指令的地址被拷贝到R14(即LR) 中,**然后跳转到指定地址运行程序。跳转范围限制在当前指令的±32M字节地址内。指令格式如下:
BL{cond} Label
带状态切换的分支指令——BX指令,该指令可以根据跳转地址(Rm)的最低位来切换处理器状态,bit[0]=0为ARM状态,否则为Thumb状态。其跳转范围限制在当前指令的±32M字节地址内(ARM指令为字对齐,最低2位地址固定为0)。指令格式如下:
BX{cond} Rm
跳转地址Rm[0] 跳转后 CPSR标志T位 处理器状态 0 0 ARM 1 1 Thumb
应用示例
B WAITA ; 跳转到WAITA标号处
ADRL R0,ThumbFun+1 ;将Thumb程序的入口地址加1 ;存入R0(ADRL地址读取伪指令)
BX R0 ;跳转到R0指定的地址,并根据R0的最低位来切换处理器状态
杂项指令(MRS,MSR)
ARM指令集中有三条指令作为杂项指令,实际上这三条指令非常重要。
助记符 | 说明 | 操作 | 条件码位置 |
---|---|---|---|
SWI immed_24 | 软中断指令 | 产生软中断,处理器进入管理模式 | SWI{cond} |
MRS Rd,psr | 读状态寄存器指令 | Rd←psr,psr为CPSR或SPSR | MRS{cond} |
MSR psr_fields, Rd/#immed_8r | 写状态寄存器指令 | psr_fields←Rd/#immed_8r,psr为 CPSR或SPSR | MSR{cond} |
[!important]
SWI指令用于产生软中断,从而实现从用户模式变换到管理模式,并且将CPSR保存到管理模式的SPSR中,然后程序跳转到SWI异常入口。
在其它模式下也可使用SWI指令,处理器同样地切换到管理模式。 在SWI异常中断处理程序中,取出SWI指令的中断立即数的步骤为:
- 首先确定引起软中断的SWI指令是ARM指令还是Thumb指令,这可通过对SPSR访问得到;
- 然后取得该SWI指令的地址,这可通过访问LR寄存器得到;
- 接着读出该SWI指令,分解出立即数。
在ARM处理器中,**只有MRS指令可以对状态寄存器CPSR和SPSR进行读操作。**通过读CPSR可以了解当前处理器的工作状态。读SPSR寄存器可以了解到进入异常前的处理器状态。该指令不影响条件码
MRS{cond} Rd,psr
注意:
- 在ARM处理器中,只有MRS指令可以将状态寄存器CPSR或SPSR读出到通用寄存器中
- MRS与MSR配合使用,实现CPSR或SPSR寄存器的读-修改-写操作,可用来进行处理器模式切换、允许/禁止IRQ/FIQ中断等设置。
- 另外,当进程切换或允许异常中断嵌套时,也需要使用MRS指令来读取SPSR状态值,并保存起来。
在ARM处理器中,只有MSR指令可以对状态寄存器CPSR和SPSR进行写操作。与MRS配合使用,可以实现对CPSR或SPSR寄存器的读-修改-写操作,可以切换处理器模式、或者允许/禁止IRQ/FIQ中断等。
指定传送的区域(fields),可以为以下字母(必须小写)的一个或者组合:
- c 控制域屏蔽字节(psr[7…0])
- x 扩展域屏蔽字节(psr[15…8])
- s 状态域屏蔽字节(psr[23…16])
- f 标志域屏蔽字节(psr[31…24])
应用举例
SWI 0 ;软中断,中断立即数为0
SWI 0xl23456 ;软中断,中断立即数为0xl23456
MRS R1,CPSR ;将CPSR状态寄存器读取,保存到R1中
MRS R2,SPSR ;将SPSR状态寄存器读取,保存到R2中
;子程序:使能IRQ中断
ENABLE_IRQ
MRS R0, CPSR
BIC R0, R0,#0x80
MSR CPSR_c,R0
MOV PC,LR
;1.将CPSR寄存器内容读出到R0;
;2.修改对应于CPSR中的I控制位;
;子程序:禁能IRQ中断
DISABLE_IRQ
MRS R0, CPSR
ORR R0, R0,#0x80
MSR CPSR_c,R0
MOV PC,LR
;3.将修改后的值写回 CPSR寄存器的对应控制域;
;4.返回上一层函数;
[!CAUTION]
说明:
只有在特权模式下才能修改状态寄存器。
程序中不能通过MSR指令直接修改CPSR中的T控制位来实现ARM状态/Thumb状态的切换,必须使用BX指令完成处理器状态的切换(因为BX指令属分支指令,它会打断流水线状态,实现处理器状态切换)。
MRS与MSR配合使用,实现CPSR或SPSR寄存器的读—修改—写操作,可用来进行处理器模式切换、允许/禁止IRQ/FIQ中断等设置。
伪指令(仅需掌握 LDR 伪指令)
**LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。**在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。
LDR{cond} register,=[expr | label_expr
[!caution]
label_expr:基于PC的地址表达式或外部表达式
register:加载的目标寄存器
注意:
1.从指令位置到文字池的偏移量必须小于4KB;
2.与ARM指令的LDR相比,伪指令的LDR的参数有“=”号。
文字池(Literal pools)其实就是一个存储常量数据的地方,汇编器会使用文字池来在代码段中存储常量数据MOV/MVN
可以直接装载一些特定范围的32位值到寄存器中,这些值包括:
(1) 8位常量,即0–255
(2) 8位常量右移偶数位
(3) MVN可以处理(1)(2)中值的按位取反值LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。
应用实例
LDR R2, =0xFF0 ;MOV R2, #0xFF0
LDR R0, =0xFF000000 ;MOV R0, #0xFF000000
LDR R1, =0xFFFFFFFE ;MVN R1, #0x1
;使用伪指令将程序标号InitStack的地址存入R1
...
LDR R1,=InitStack
...
InitStack
MOV R0, LR
...