ARM指令和指令系统:
ARM指令在机器中的表示格式是用32位的二进制数表示。如ARM中有一条指令为
ADDEQS R0,R1,#8;
其二进制代码形式为:
31~28
|
27~25
|
24~21
|
20
|
19~16
|
15~12
|
11~0
|
0000
|
001
|
0100
|
1
|
0001
|
0000
|
0000 0000 1000
|
cond
|
opcode
|
Rn
|
Rd
|
op2
|
ARM指令格式一般如下:
格式中< >的内容是必不可少的,{ }中的内容可忽略
表示操作码。如ADD表示算术加法
{} 表示指令执行的条件域。如EQ、NE等,缺省为AL。
{S} 决定指令的执行结果是否影响CPSR的值,使用该后缀则指令执行结果影响CPSR的值,否则不影响
表示目的寄存器
表示第一个操作数,为寄存器
表示第二个操作数,可以是立即数。寄存器和寄存器移位操作数
ARM指令后缀:S、!
S后缀:指令中使用S后缀时,指令执行后程序状态寄存器(CPSR)的条件标志位将被刷新,不使用S后缀时,指令执行后程序状态寄存器的条件标志将不会发生变化。S后缀常用于对条件进行测试,如是否有溢出,是否进位等,根据这些变化,就可以进行一些判断,如是否大于,相等,从而影响指令执行的顺序。
!后缀:如果指令地址表达式中不含!后缀,则基址寄存器中的地址值不会发生变化。加上此后缀后,基址寄存器中的值(指令执行后) = 指令执行前的值 + 地址偏移量
(1) !后缀必须紧跟在地址表达式后面,而地址表达式要有明确的地址偏移量
(2) !后缀不能用于R15(PC)的后面
(3) 当用在单个地址寄存器后面时,必须确信这个寄存器有隐性的偏移量,例如“STMDB R1!, {R3,R5,R7}”。此时地址基址寄存器R1的隐性偏移量为4(一条指令占32位,即4个字节)
指令的条件码:31-28位4个字节存储,共16个条件码
条件码
|
后缀
|
标志
|
含义
|
0000
|
EQ
|
Z置位
|
相等
|
0001
|
NE
|
Z清零
|
不相等
|
0010
|
CS
|
C置位
|
无符号数大于或等于
|
0011
|
CC
|
C清零
|
无符号数小于
|
0100
|
MI
|
N置位
|
负数
|
0101
|
PL
|
N清零
|
正数或零
|
0110
|
VS
|
V置位
|
溢出
|
0111
|
VC
|
V清零
|
未溢出
|
1000
|
HI
|
C置位 Z清零
|
无符号数大于
|
1001
|
LS
|
C清零 Z置位
|
无符号数小于或等于
|
1010
|
GE
|
N等于V
|
带符号数大于或等于
|
1011
|
LT
|
N不等于V
|
带符号数小于
|
1100
|
GT
|
Z清零且(N等于V)
|
带符号数大于
|
1101
|
LE
|
Z置位或(N不等于V)
|
带符号数小于或等于
|
1110
|
AL
|
忽略
|
无条件执行
|
ARM指令分类:六大类
ARM指令集可以分为数据处理指令,数据加载指令和存储指令,分支指令,程序状态寄存器(PSR)处理指令,协处理器指令和异常产生指令六大类。
ARM指令的寻址方式:8类
ARM指令的寻址方式一般可以分为8类:立即数寻址,寄存器寻址,寄存器间接寻址,寄存器移位寻址,基址变址寻址,多寄存器寻址,相对寻址,堆栈寻址等
举例:
MOV R0, #15 ;立即数15放入寄存器R0中
ADD R0, R1, R2 ;R0 <= R1+R2
LDR R0, [R4] ;R0 <= [R4](R4中存放的是一个指针变量,[ ]表示取改地址值指向的内容)
ADD R0, R1, R2, LSL #1 ;R0 <= R1+R2(R2左移一位后的值)
MOV R0, R1, LSL R3 ;R0 <= R1(R1左移R3位后)
LDR R0, [R1, #4] ;R0 <= [R1+4]
LDR R0, [R1, #4]! ;R0 <= [R1+4],R1 <= R1+4。同时更新基址
LDR R0, [R1], #4 ;R0 <= [R1],R1 <= R1+4
LDR R0, [R1, R2] ;R0 <= [R1+R2]
LDMIA R0!, {R1 - R4} ;R1 <= [R0]、R2 <= [R0+4]、R3<= [R0+8]、R4 <= [R0+12]
BL proc ;跳转到子程序proc处执行,执行完毕后返回(L标记,带返回的跳转)。
STMFD R13!, {R0 - R4} ;R0-R4压栈,FD为满栈递减,地址从高到低,R13为SP堆栈指针
LDMFD R13!, {R0 - R4} ;R0-R4出栈,FD为满栈递减
ARM寄存器数据处理指令:
1、数据处理指令机器编码格式:
31~28
|
27~26
|
25
|
24~21
|
20
|
19~16
|
15~12
|
11~0
|
cond
|
00
|
I
|
opcode
|
S
|
Rn
|
Rd
|
op2
|
cond:指令执行的条件码
I:用于区别第二操作数是立即数(I=1)还是寄存器移位(I=0)
opcode:数据处理指令操作码
S:用于设置条件码,S=0时,条件码不改变,S=1时,条件码根据具体指令的结果修改
Rn:第一操作数寄存器
Rd:目标寄存器
op2:第二操作数,该数可以是立即数或寄存器移位数
2、数据传送指令:MOV MVN
MOV R1, R0 ;将寄存器R0的值传送到寄存器R1
MOV PC, R14 ;将寄存器R14的值传送到PC,常用于子程序返回
MOV R1, R0, LSL #3 ;将寄存器R0的值左移3位后传送到R1
MOV R0, #5 ;将立即数5传送到寄存器R0
MVN R0, #0 ;将立即数0按位取反后传送到寄存器R0中,完成后R0 = -1
MVN R1, R2 ;将R2按位取反后,结果存到R1
3、移位指令:LSL、LSR、ASR、ROR、RRX
MOV R0, R1, LSL #1 ;寄存器R1左移一位后传送到R0
MOVS R0, R1, LSL #1 ;寄存器R1左移一位后传送到R0,并更新状态标志位
MOVS R0, R1, LSR #1 ;寄存器R1右移一位后传送到R0,并更新状态标志位
MOVS R0, R1, ASR #1 ;寄存器R1算术右移一位后传送到R0,并更新状态标志位,算术右移,第一位符号位不变
MOV R0, R1, ROR #1 ;寄存器R1循环右移一位后传送到R0
MOVS R0, R1, RRX ;寄存器R1循环右移一位后传送到R0,用C标志位作为最高位扩展
4、算术指令:ADD、ADC、SUB、SBC、RSB、RSC
ADD R0, R1, R2 ;寄存器R1和R2的值相加后传送到R0
ADD R0, R1, #5 ;寄存器R1的值加上5后传送到R0
ADD R0, R1, R2, LSL #2 ;寄存器R2左移两位后与R1相加,得到的结果传送到R0
ADD R0, R1, R2, LSL R3 ;寄存器R2左移R3后与R1相加,得到的结果传送到R0
ADDS R0, R2, R4 ;64位加,低位放在R0
ADC R1, R3, R5 ;64位加,高位放在R1,注意要加上低位的进位
SUB R0, R1, R2 ;寄存器R1和R2的值相减后传送到R0
SUB R0, R1, #6 ;寄存器R1的值减6后传送到R0
SUB R0, R1, R2, LSL #1 ;R1与寄存器R2左移一位后的值相减,得到的结果传送到R0
SUBS R0, R2, R4 ;64位减,低位放在R0
SBC R1, R3, R5 ;64位减,高位放在R1,注意要减去低位的借位
RSB R0, R1, R2 ;寄存器R2和R1的值相减后传送到R0,注意是R2-R1,方向相反
RSB R0, R1, #6 ;6与寄存器R1的值相减后传送到R0
RSB R0, R1, R2, LSL #1 ;寄存器R2左移一位后与R1相减,得到的结果传送到R0
RSC R0, R1, R2 ;寄存器R2和R1的值相减,再减去借位后传送到R0
5、逻辑运算指令:AND、ORR、EOR、BIC
AND R0, R0, #0xF ;R0的值与0xF相与后的值传送到R0
ORR R0, R0, #9 ;R0的值与9相或后的值传送到R0
EOR R0, R0, #0xF ;R0的值与0xF相异或后的值传送到R0
BIC R0, R0, #9 ;位清除指令R0的第0位和第3位清零
6、比较指令:CMP、CMN、TST、TEQ
CMP R1, #10 ;将寄存器R1的值与10相减,并设置CPSR标志位
ADDGT R0, R0, #5 ;如果R1>10,则执行ADDGT指令,将R0加5
CMN R0, R1 ;R0 - (-R1),反值比较,影响CPSR标志位
CMN R0, #10 ;R0 - (-10),反值比较,影响CPSR标志位
TST R1, #3 ;检查R1中第0位和第1位是否为1,根据结果更新条件标志位
TEQ R1, R2 ;将寄存器R1的值与寄存器R2的值进行按位异或,并根据结果设置CPSR的标志位
7、乘法指令:MUL、MLA、SMULL、SMLAL、UMULL、UMLAL
MUL R0, R1, R2 ;R1和R2相乘的结果发送到R0
MULS R0, R1, R2 ;R1和R2相乘的结果发送到R0,同时设置CPSR的相关条件标志位
MLA R0, R1, R2, R3 ;R1和R2相乘的结果再加上R3后发送到R0
MLAS R0, R1, R2, R3 ;R1和R2相乘的结果再加上R3后发送到R0,更新CPSR标志位
SMULL R0, R1, R2, R3 ;R2和R3相乘的结果的低32位放在R0,高32位放在R1
SMLAL R0, R1, R2, R3 ;R2和R3相乘的结果的低32位加上R0后放在R0,高32位加上R1后放在R1
UMULL R0, R1, R2, R3 ;无符号数相乘,结果与SMULL类似
UMLAL R0, R1, R2, R3 ;无符号数乘加,结果与SMLAL类似
ARM数据加载和存储指令:
1、数据加载和存储的方向。
寄存器到存储器方向:Store;存储器到寄存器方向:Load
数据加载和存储指令共有三种类型:单寄存器加载和存储指令,多寄存器加载和存储指令 和 交换指令
2、数据加载与存储器指令寻址
LDR R5, [R6, #0x08] ;R6寄存器加0x08的和的地址值内的数据传送到R5
STR R6, [R7], #-0x08 ;R6寄存器的数据传送到R7存储的地址值指向的存储空间,同时更新R7寄存器的内容为R7-0x08
LDR R5, [R6, R3] ;R6寄存器加R3的和的地址值内的数据传送到R5
STR R6, [R7], -R8 ;R6寄存器的数据传送到R7存储的地址值指向的存储空间,同时更新R7寄存器的内容为R7-R8
LDR R3, [R2, R4, LSL #2] ;R3 <== [ R2 + R4(R4左移两位) ]
LDR R3, [R2], -R4, LSR #3 ;R3 <== [ R2 ],R2 = R2-R4(R4右移三位)
LDR R4, START ;将标号START标定的空间的数据加载到R4中
3、地址索引:前索引、自动索引、后索引
前索引:前索引也称为前变址,这种索引是在指令执行前把偏移量和基址相加减,得到的值作为变量的地址。如:
LDR R5, [R6, #0x04]
STR R0, [R5, -R8]
自动索引:自动索引也称为自动变址,有时为了修改基址寄存器的内容,使之指向数据传送地址,可使用这种方法自动修改基址寄存器,如:
LDR R5, [R6, #0x04]!
后索引:后索引也被称为后变址,后索引就是用基址寄存器的地址值寻址,找出操作数进行操作,操作完成后,再把地址偏移量和基址相加/减,结果送到基址寄存器,作为下一次寻址的基址。如:
LDR R5, [R6], #0x04
STR R6, [R7], #-0x08
4、单寄存器加载和存储指令:LDR/STR、LDRB/STRB、LDRH/STRH、LDRSB/LDRSH
a. 字数据加载/存储指令格式:
31~28
|
27~26
|
25
|
24
|
23
|
22
|
21
|
20
|
19~16
|
15~12
|
11~0
|
cond
|
01
|
I
|
P
|
U
|
B
|
W
|
L
|
Rn
|
Rd
|
op2
|
cond:指令执行的条件编码
I、P、U、W:用于区别不同的地址模式(偏移量)。
I为0时,偏移量为12位立即数;I为1时,偏移量为移位寄存器移位
P表示前/后索引
U表示加/减
W表示回写
L:L为1时表示加载,L为0时表示存储
B:B为1表示字节访问,B为0表示字访问
Rd:源/目标寄存器
Rn:基址寄存器
op2:表示偏移量是一个12位的无符号二进制数,与Rn一起构成地址addr
b. 存储器 <==> 寄存器 LDR/STR
LDR指令用于从存储器中间一个32位的字数据加载到目的寄存器Rd中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当做目的地址,从而实现程序流程的跳转。
LDR R4, START ;将存储地址为START的字数据读入R4
STR R5, DATA1 ;将R5存入存储地址为DATA1中
LDR R0, [R1] ;将存储器地址为R1的字数据读入寄存器R0
LDR R0, [R1, R2] ;将存储器地址为R1+R2的字数据读入寄存器R0
LDR R0, [R1, #8] ;将寄存器R1+8的内容读入寄存器R0
LDR R0, [R1, R2, LSL #2] ;将R1+R2*4的字数据读入寄存器R0
STR R0, [R1, R2]! ;将R0字数据存入存储器地址为R1+R2的存储单元中,并将新地址R1+R2写入R1
STR R0, [R1, #8]! ;将R0字数据存入存储器地址为R1+8的存储单元中,并将新地址R1+8写入R1
STR R0, [R1, R2, LSL #2]! ;将R0字数据存入地址为R1+R2*4的存储单元中,并将新地址R1+R2*4写入R1
LDR R0, [R1], #8 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+8写入R1
LDR R0, [R1], R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR R0, [R1], R2, LSL #2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2*4写入R1
【备注】注意事项:
a) 立即数绝对值不大于4095的数值,可使用带符号数,即在-4095 ~ +4095之间。(4096D = 1000H)
b) 语句的标号不能指向程序存储器的程序存储区,而是指向程序存储器的数据存储区或数据存储器的数据存储区。另外指向的区域是可修改的。例如,在用户模式下,有些存储区是不能访问的或是只读的。
c) 字传送时,偏移量必须保证偏移的结果能够使地址对齐。
d) 使用寄存器移位的方法计算偏移量时。移位的位数不能超过规定的数值,而且不能用寄存器表示移位的位数。各类移位指令的移位位数规定如下:
ASR #n:算术右移(1≤n≤32)
LSL #n:逻辑左移(0≤n≤31)
LSR #n:逻辑右移(1≤n≤32)
ROR #n:循环右移(1≤n≤31)
e) R15作为基址寄存器Rn时,不可以使用回写功能,即使用后缀“!”,另外,R15不可作为偏移寄存器使用。
5、字节数据加载/存储指令:LDRB/STRB
LDRB指令用于从存储器中将一个8位字节的数据加载到目的寄存器,同时将寄存器的高24位清零。该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器读取的数据被当做目的地,从而可以实现程序流程的跳转
STRB指令用于从源寄存器中将一个8位的字节数据存储到存储器中,该字节数据为源寄存器的低8位,STRB指令和LDRB指令的区别在于数据的传送方向。
LDRB R0, [R1] ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零。
LDRB R0, [R1, #8] ;将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零。
STRB R0, [R1] ;将寄存器R0中的字节数据写入以R1为地址的存储器中。
STRB R0, [R1, #8] ;将寄存器R0中的字节数据写入以R1+8为地址的存储器中。
6、LDRH/STRH 半字数据加载/存储指令
LDR指令用于从寄存器中间一个16位的半字数据加载到目的寄存器Rd中,同时将寄存器的高16位清零,该指令通常用于从存储器中读取16位的半字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的数据被当做目的地址,从而可以实现程序流程的跳转。
LDRH R0, [R1] ;将存储器地址R1的半字数据读入寄存器R0,并将R0的高16位清零
LDRH R0, [R1, #8] ;将存储器地址为R1+8的半字数据读入寄存器R0,并将R0的高16位清零
LDRH R0, [R1, R2] ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将R0的高16位清零
STRH R0, [R1] ;将寄存器R0中的半字数据写入以R1为地址的存储器中
使用半字加载/存储指令需要注意的事项:
(1) 必须半字地址对齐。
(2) 对于R15的使用需要慎重,R15作为基址寄存器Rn时,不可以使用回写功能,不可使用R15作为目的寄存器。
(3) 立即数偏移使用的是8位无符号数。
(4) 不能使用寄存器移位寻址
7、有符号数字节/半字加载指令:LDRSB / LDRSH
LDRSB指令用于从存储器中间一个8位的字节数据加载到目的寄存器中,同时将寄存器的高24位设置为该字节数据的符号位的值,即将该8位字节数据进行符号位的扩展,生成32位数据;
LDRSH指令用于从存储器中将一个16位的半字数据加载到目的寄存器Rd中,同时将寄存器的高16位设置为该字数据的符号位的值,即将该16位字数据进行符号位的扩展,生成32位数据。
LDRSB R0, [R1, #4] ;将存储地址为R1+4的有符号字节数据读入R0,R0中的高24位设置为高字节数据的符号位
LDRSH R6, [R2], #2 ;将存储地址为R2+2的有符号半字数据读入R6,R6的高16位设置成该字节数据的符号位,R2=R2+2
8、多寄存器加载和存储指令:LDM / STM
LDM指令用于从基址寄存器所指示的一片连续存储器中读取数据到寄存器列表所指示的多个寄存器中,内存单元的其实地址为基址寄存器Rn的值,各个寄存器有寄存器列表regs表示。该指令一般用于多个寄存器数据的出栈操作;STM指令用于将寄存器列表所指示的多个寄存器的值存入到由基址寄存器所指示的一片连续存储器中,内存单元的其实地址为基址寄存器Rn的值,各个寄存器由寄存器列表regs表示。指令的其它参数的用法和LDM指令是相同的。该指令一般用于多个寄存器数据的进栈操作。
用于数据的存储和读取有一下几种情况:
Type
|
含义
|
IA
|
每次传送后,地址值加
|
IB
|
每次传送前,地址值加
|
DA
|
每次传送后,地址值减
|
DB
|
每次传送前,地址值减
|
对于堆栈操作有如下几种情况:
Type
|
含义
|
FD
|
满递减堆栈
|
ED
|
空递减堆栈
|
FA
|
满递增堆栈
|
EA
|
空递增堆栈
|
(栈指针指向栈顶元素(即最后一个入栈的数据元素)时称为FULL栈;栈指针指向与栈顶元素相邻的下一个空单元时称为EMPTY栈)
{ ! } 为可选后缀,若选用该后缀,则当数据加载与存储完毕后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{ ^ } 为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常数据加载和存储之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
LDMIA R0!, {R6-R8} ;R6 <- [R0],R7 <- [R0+4],R8 <- [R0+8],R0 <- R0+12
LDMIB R0!, {R6-R8} ;R6 <- [R0],R7 <- [R0+4],R8 <- [R0+8],R0 <- R0+8
9、堆栈和堆栈操作
堆栈就是在RAM存储器中开辟(指定)的一个特定的存储区域,在这个区域中,信息的存入(此时称为推入)与取出(此时称为弹出)的原则不再是“随机存取”,而是按照“后进先出”的原则就行存取。
a) 建栈:规定堆栈底部在RAM存储器中的位置,如:用户可以通过LDR命令设置SP的值来建立堆栈。
LDR R13, =0x90010 ;设置栈指针sp = 0x90010
LDR SP, =0x90010 ;设置栈指针sp = 0x90010
这时,SP指向地址0x90010,栈内无数据,堆栈底部与顶部重叠,是一个空栈。
b) 进栈:STM指令配合FD(满递减)、ED(空递减)、FA(满递增)、EA(空递增)完成入栈操作。在使用一个堆栈的时候,需要确定堆栈在存储器空间中是向上生长还是向下生长的。向上称为递增,向下称为递减。
STMFD SP!, {R2-R4} ;把R4,R3,R2的值依次压栈(标号高的存在高地址)
LDMFD SP!, {R6-R8} ;把R2,R3,R4的值,依次退到R6,R7,R8
10、SWP交换指令
支持在存储器和寄存器之间交换数据
SWP 字数据交换指令
SWPB 字节数据交换指令
SWP{B}{条件} 目的寄存器, 源寄存器, [源寄存器2]
SWP R0, R1, [R2]
;将R2所指的存储器中的字数据传送到R0,同时将R1中的字数据传送到R2所指的存储器单元。
显然,当源寄存器1与目的寄存器是同一个寄存器时,就完成了寄存器与存储器间的交换操作。
SWPB指令用于将源寄存器2所指向的存储器中的字节数据到目的寄存器中,目的
寄存器的高24bit位清零,同时将源寄存器1中的
低8位数据传送到所指向的存储器中。
分支指令
用于实现程序流程的跳转,在ARM程序中有两种方法可以实现程序流程的跳转:
1) 是使用专门的跳转指令
2) 是直接向程序计数器PC写入跳转地址值。
第二种方法可以实现在4GB的地址空间中的任意跳转,
在跳转之前结合使用 MOV LR, PC 等类似指令,可以保存将来的返回地址值,
从而实现在4GB连续的线性地址空间的子程序调用。
ARM指令集中的跳转指令可以完成从当前指令
向前或向后的32MB(25bit)的地址空间的跳转,包括以下4条指令:
B 跳转指令
BL 带返回的跳转指令
BX 带状态切换的跳转指令
BLX 带返回和状态切换的跳转指令
B指令的格式为:
B{条件} 目标地址
B指令是最简单的跳转指令。一旦遇到一个B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。
B Label程序无条件跳转到标号Label处执行
CMP R1, #0
BEQ Label
;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行。
BL指令的格式为:
BL{条件} 目标地址
BL是另一个跳转指令,但跳转之前,
会在寄存器R14中保存PC当前值,因此,可以通过将R14 的内容重新加载到PC中,
来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。
BL Label当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14中
BX指令的格式为:
BX{条件} 目标地址
BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
BX指令中所指定的目标地址,
只能使用寄存器的寻址方式,即跳转的目标地址应先保存在一个寄存器中。
指令在实现跳转的同时,完成处理器的工作状态的切换(ARM状态与Thumb状态间的切换)。
BX指令中,用寄存器的最低位来指示切换到哪一个工作状态。
如寄存器
最低位为1,则把目标地址处的代码解释为Thumb代码,
进入Thumb工作状态,并自动将CPSR中的控制位T置1。
若寄存器
最低位为0,则把目标地址处的代码解释为ARM代码,
进入ARM工作状态,并自动将CPSR中的控制位T置0。
BLX指令的格式为:
BLX 目标地址
BLX指令 从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由
ARM状态切换到Thumb状态,该指令同时将
PC的当前内容保存到寄存器R14中。
因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。
同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。
ADRL R0, ThumbFun+1
;生成分支地址并置最低位为 1
BX R0
;跳转到R0所指定的地址,并切换处理器到Thumb工作状态
程序状态寄存器(PSR)处理指令
ARM微处理器支持程序状态寄存器访问指令,用于在程序状态寄存器和通用寄存器之间传送数据。
使用环境:
1) 当需要改变程序状态寄存器的内容时,可用MRS将状态寄存器的内容读入到通用寄存器,修改后再写回到程序状态寄存器。
2) 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
MRS{条件} 通用寄存器, 程序状态寄存器(CPSR、SPSR)
将状态寄存器的内容传送到通用寄存器。
MSR{条件} 程序状态寄存器(CPSR、SPSR)_<域>, 操作数
将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。
<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器分为4个域:
f域:位31~位24为条件标志位域;
s域:位23~位16为状态位域;
x域:位15~位8为扩展位域;
c域:位7~位0为控制位域;
该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。
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
协处理器指令
1、CDP 指令
CDP 指令的格式为:
CDP{条件} 协处理器编码, 协处理器操作码1, 目的寄存器, 源寄存器1, 源寄存器2, 协处理器操作码2
CDP 指令用于ARM 处理器通知ARM 协处理器执行特定的操作,若协处理器不能成功完成特定的操作,则产生未定义指令异常。
其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,目的寄存器和源寄存器均为协处理器的寄存器,
指令不涉及ARM 处理器的寄存器和存储器。
CDP P3, 2, C12, C10, C3, 4
;该指令完成协处理器 P3 的初始化
2、LDC 指令
LDC 指令的格式为:
LDC{条件}{L} 协处理器编码, 目的寄存器, [源寄存器]
LDC 指令用于将源寄存器所指向的存储器中的字数据传送到目的寄存器中,若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指令为长读取操作,如用于双精度数据的传输。
LDC P3, C4, [R0]
;将 ARM 处理器的寄存器 R0 所指向的存储器中的字数据传送到协处理器 P3 的寄存器 C4 中。
3、STC 指令
STC 指令的格式为:
STC{条件}{L} 协处理器编码, 源寄存器, [目的寄存器]
STC 指令用于将源寄存器中的字数据传送到目的寄存器所指向的存储器中,若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指令为长读取操作,如用于双精度数据的传输。
STC P3, C4, [R0] ;
将协处理器 P3 的寄存器 C4 中的字数据传送到 ARM 处理器的寄存器R0 所指向的存储器中。
4、MCR 指令
MCR 指令的格式为:
MCR{条件} 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2
MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。
MCR P3, 3, R0, C4, C5, 6
;该指令将 ARM 处理器寄存器 R0 中的数据传送到协处理器 P3 的寄存器 C4 和 C5 中。
5、MRC 指令
MRC 指令的格式为:
MRC{条件} 协处理器编码, 协处理器操作码1, 目的寄存器, 源寄存器1, 源寄存器2, 协处理器操作码2
MRC 指令用于将协处理器寄存器中的数据传送到ARM 处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,目的寄存器为ARM 处理器的寄存器,源寄存器1 和源寄存器2 均为协处理器的寄存器。
MRC P3, 3, R0, C4, C5, 6
;该指令将协处理器 P3 的寄存器中的数据传送到 ARM 处理器寄存器中。
异常产生指令
异常进入及返回:
1. 当异常产生时:
a) 拷贝当前模式的CPSR值到相应异常模式的SPSR_,如:CPSR(usr) --> SPSR_svc(svc)。
b) 设置适当的CPSR位:
改变处理器状态进入ARM状态
改变处理器模式进入相应的异常模式
如果需要可以设置中断禁止位禁止相应中断
c) 保存返回地址
(pc-4)到LR_
(PC本来指向当前执行指令位置+8)
d) 设置pc为相应的异常向量
2. 当异常返回时:
a) 从SPSR_恢复CPSR
b) 从LR_恢复PC
(只能在ARM状态下实现该返回操作)
异常返回的指令分析:
* 使用一数据处理指令实现,该指令带“S”后缀,同时PC作为目的寄存器
* 在特权模式下不仅仅要更新PC,而且还要拷贝SPSR到CPSR
1. 从SWI和Undef异常返回:
movs pc, lr
这两种异常都会在导致异常的指令执行周期中就进入异常,没有等到下个时钟周期才进入异常,另外这两种异常都是返回到产生异常指令的下一条指令去继续执行。我从前面可以知道lr中保存的就是pc-4(该pc值是产生异常指令的下下一条指令的地址),所以可以直接将lr的值送入pc。
2. 从FIQ、IRQ和Prefect Abort返回:
subs pc, lr, #4
这三种异常都会等待产生异常的指令执行完才会进入异常,所以此刻的PC已经更新,比如:
...
subs r3, r3, #1<<26 @ 0x100
bcs 2b @ 0x104
subs r1, r1, #1<<5 @ 0x108
bcs 1b @ 0x10b
...
如果在执行第1行地址为0x100指令时,发生了上面三种异常,此刻的pc值为0x108,等第1行执行完之后,pc更新为0x10b。同时进入异常处理,在异常进入时将lr值保存为pc-4,即0x108。那么在异常返回后,需要接着执行发生异常指令的下一条指令的话就必须将lr的值减去4才能得到正确的地址,lr-4 = 0x108 - 4 = 0x104。
3. 从数据异常(Data Abort)异常返回:
该异常也是会等待产生异常的那条指令执行完才会进入异常,情况类似于第2类的三种异常,但是有一点不同的是:数据异常返回地址不是产生异常的下一条指令,而是产生异常的那条指令,所以,它的返回指令应该是:
subs pc, lr, #8
按照上面的例子就应该返回到地址0x100处继续执行。为什么会这样,因为数据异常返回后会继续去取数据,想想缺页异常。
4. 如果lr在进入异常后被压栈的话,就需要使用下面的指令来弹出:
LDMFD sp! , {pc}^
(^同时拷贝SPSR到CPSR中,这里的lr在压栈之前已经做了前面3中情况对应的处理了)
SWI异常:
执行SWI软中断指令即可产生软中断异常,
进入SWI异常时会做如下动作:
1. CSPR保存到SPSR_svc。
2. 改变处理器状态进入ARM状态
3. 改变处理器模式进入相应的管理员模式(svc)
4. 根据需要禁止中断
5. 保存返回地址(pc-4)到LR_svc
6. 设置pc为0x08或者0xFFFF0008(异常向量地址)
** 需要注意一点的是:如果在执行SWI指令时系统正处于svc模式时,那么将会覆盖掉原来LR_svc的值。所以
在SWI指令之前应该对LR_svc压栈保存。
SWI异常返回时,做如下动作:
从SPSR_svc恢复CPSR
从LR_svc恢复PC,不需要修正
在c语言中使用关键字“
__swi”来定义一个软中断函数:
__swi(0x30) void my_swi(void);
void fun(void)
{
my_swi();
}
-----> 转换成汇编
fun:
STMFD sp!, {lr}
swi 0x24
LDMFD sp!, {pc}
swi调用带参和swi处理函数带参:
swi调用带参:
使用swi指令时,通常有两种方法来传递参数:
1. 使用swi号
swi指令的低24bits(ARM指令集)组成或者低8bits(Thumb指令集)来指定软中断号,
其余参数通过寄存器来传递。
2. r0决定软中断号,其余参数使用同样寄存器传递。
** c语言中,关键字“__swi”定义的软中断函数允许最多4个参数,使用r0~r4来传递。
swi处理函数带参:
1. 汇编中,存取调用者设置的寄存器即可
2. 传参给c,通常才用压栈的方法:
将参数压栈,给调用的函数传递一个指向这些参数的指针。
获取SWI号:
ARM core不提供直接传递软中断号到处理程序的机制,SWI处理程序必须定位SWI指令并提取SWI指令中的常数域
1. 检查SPSR_svc的Tbit,可以确定陷入swi异常之前的指令时ARM指令集还是Thumb指令集。
2. 然后通过LR_svc的值确定
SWI指令的地址。
ARM状态下是LR-4,而
Thumb状态下是LR-2的位置。
3. SWI指令格式:
ARM态:
31~28
|
27~24
|
23~0
|
cond
|
1111
|
SWI number
|
Thumb态:
15~8
|
7~0
|
11011111
|
SWI number
|
ARM and Thumb-2 Instruction Set Quick Reference Card.pdf