当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时,指令被执行,否则指令被忽略。
每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。条件码共有16种,每种条件码可用两个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。例如,跳转指令B可以加上后缀EQ变为BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转。
1、 B指令
B指令的格式为:
B{条件} 目标地址
B指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB的地址空间)。以下指令:
B Label ;程序无条件跳转到标号Label处执行
CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label
3.3.6 批量数据加载/存储指令
ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:
— LDM 批量数据加载指令
— STM 批量数据存储指令
LDM(或STM)指令
LDM(或STM)指令的格式为:
LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}为以下几种情况:
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;
FA 满递增堆栈;
EA 空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
指令示例:
STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。
常用ARM指令
1、 内存访问指令
基本指令:
LDR:memory -> register (memory包括映射到内存空间的非通用寄存器)
STR:register -> memory
语法:
op{cond }{B}{T} Rd , [Rn ]
op{cond }{B} Rd , [Rn , FlexOffset ]{!}
op{cond }{B} Rd , label
op{cond }{B}{T} Rd , [Rn ], FlexOffset
op:基本指令,如LDR、STR
cond:条件执行后缀
B:字节操作后缀
T:用户指令后缀
Rd:源寄存器,对于LDR指令,Rd将保存从memory中读取的数值;对于STR指令,Rd保存着将写入memory的数值
Rn:指针寄存器
FlexOffset:偏移量
例子:
ldr r0, [r1] ;r1作为指针,该指针指向的数存入r0
str r0, [r1, #4] ;r1+4作为指针,r0的值存入该地址
str r0, [r1, #4]! ;同上,并且r1 = r1 + 4
ldr r1, =0x08100000 ;立即数0x08100000存到r1
ldr r1, [r2], #4 ;
【label的使用】
addr1 ;定义一个名为“addr1”的label,addr1 = 当前地址
dcd 0 ;在当前地址出定义一个32bit的变量
~ ~ ~
ldr r1, label1 ;r1 = addr1,r1即可以作为var1的指针
ldr r0, [r1]
add r0, r0, #1
str r0, [r1] ;变量var1的值加1
【FlexOffset的使用】
FlexOffset可以是立即数,也可以是寄存器,还可以是简单的表达式
2、 多字节存取指令(常用于堆栈操作)
基本指令:
LDM:memory ――> 多个寄存器
STM:多个寄存器 ――> memory
语法:
op{cond }mode Rn{!}, reglist {^}
mode:指针更新模式,对应于不同类型的栈。最常用的是“FD”模式,相当于初始栈指针在高位,压栈后指针值减小。
Rn:指针寄存器
!:最后的指针值将写入Rn中
reglist:要操作的寄存器列表,如{r0-r8, r10}
^ :完成存取操作后从异常模式下返回
例子:
;异常处理程序:
sub lr, lr, #4 ; lr – 4是异常处理完后应该返回的地方
;保存r0~r12和lr寄存器的值到堆栈并更新堆栈指针。
stmfd sp!, {r0-r12, lr} ;异常处理
ldmfd sp!, {r0-r12, pc}^ ;从堆栈中恢复r0~r12,返回地址赋给pc指针,使程序返回到异常发生前所执行的地方,^标记用来使CPU退出异常模式,进入普通状态。
3、 算术运算指令
基本指令:
ADD:加
SUB:减
语法:
op{cond }{S} Rd, Rn, Operand2
S:是否设置状态寄存器(CPSR),如:N(有符号运算结果得负数)、Z(结果得0)、C(运算的进位或移位)、V(有符号数的溢出)等等。
Rd:保存结果的寄存器
Rn:运算的第一个操作数
Operand2:运算的第二个操作数,这个操作数的值有一些限定:如可以是8位立即数(例:0xa8)或一个8为立即数的移位(例:0xa800,而0xa801就不符合)。也可以是寄存器,或寄存器的移位(如“r2, lsl #4”)。
例子:
add r0, r1, r2 ; r0 = r1 + r2
adds r0, r1, #0x80 ; r0 = r1 + 0x80,并设置状态寄存器
subs r0, r1, #2000 ; r0 = r1 – 2000,并设置状态寄存器
4、 逻辑运算指令
基本指令:
AND:与
ORR:或
EOR:异或
BIC:位清0
语法:
op{cond }{S} Rd, Rn, Operand2
语法类似算术运算指令
例子:
ands r0, r1, #0xff00 ; r0 = r1 and 0xff00,并设置状态寄存器
orr r0, r1, r2 ; r0 = r1 and r2
bics r0, r1, #0xff00 ; r0 = r1 and ! (0xff00)
ands r0, r1, #0xffff00ff ; 错误
5、 MOV指令
语法:
MOV{cond}{S} Rd, Operand2
例子:
mov r0, #8 ; r0 = 8
mov r0, r1 ; r0 = r1
不同于LDR、STR指令,该指令可以寄存器间赋值
6、 比较指令
基本指令:
CMP:比较两个操作数,并设置状态寄存器
语法:
CMP{cond } Rn, Operand2
例子:
cmp r0, r1 ; 计算r0 – r1,并设置状态寄存器,由状态寄存器可以知r0是否大于、小于或等于r1
cmp r0, #0 ;
7、 跳转指令
基本指令:
B:跳转
BL:跳转并将下一指令的地址存入lr寄存器
语法:
op{cond} label
label:要跳向的地址
例子:
loop1
b loop1 ; 跳到地址loop1处
bl sub1 ; 将下一指令地址写入lr,并跳至sub1
sub1
mov pc, lr ; 从sub1中返回
【使用本地label(local label)】
本地label可以在一个程序段内多次使用,用数字作为label的名称,也可以在数字后面跟一些字母。引用本地label的语法是: %{F|B}{A|T}n{routname},其中F代表向前搜索本地label,B代表向后搜索,A/T不常使用。
例子
100 ; 定义本地label,名称为“100”
~ ~ ~
100 ; 第二次定义本地label,名称为“100”
~ ~ ~
b %f100 ; 向前跳到最近的“100”处
~ ~ ~
b %b100 ; 向后跳至最近的“100”处
100 ; 第三次定义本地label 100
8、 条件执行
条件:状态寄存器中某一或某几个比特的值代表条件,对应不同的条件后缀cond,如:
后缀 (cond) 状态寄存器中的标记 意义
EQ Z =1 相等
NE Z = 0 不相等
GE N和V相同 >=
LT N和V不同 <
GT Z = 0, 且N和V相同 >
LE Z = 1, 或N和V不同 <=
例子:
cmp r0, r1 ;比较r0和r1
blgt sub1 ;如果r0>r1,跳转到sub1,否则不操作
;一段循环代码
ldr r2, =8 ;r2 = 8
loop
;这里可以进行一些循环内的操作
subs r2, r2, #1 ;r2 = r2 –1,并设置状态位
bne loop ;如果r2不等于0,则继续循环
;――――――――――――――――――――
mov r0, #1 ; r0 = 1
cmp r2, #8 ; 比较r2和8
movlt r0, #2 ; 如果r2<8,r0 = 2
ARM汇编程序结构
;――――――――――――――――――――
AREA EX2, CODE, READONLY
;AREA指令定义一个程序段,名称为EX2,属性为:CODE、READONLY
INCLUDE Common.inc ;包含汇编头文件
IMPORT sub1 ;引用外部符号
EXPORT prog1 ;向外输出符号
ENTRY ;ENTRY指令定义程序的开始
start ;此处定义了一个label start
MOV r0, #10
MOV r1, #3
ADD r0, r0, r1 ;r0 =r0 +r1
prog1 ;此处定义了一个label prog1
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456
END ;END指令表示程序段的结束
;――――――――――――――――――――
宏的使用
定义宏:
MACRO ;宏的起始
{label} macroname para1,para2……
;代码
MEND ;宏结束
引用宏:
marconame para1,para2……
例子
;定义一个宏,完成两个寄存器内容交换
MACRO
swap $w1, $w2, $w3
mov $w3, $w1
mov $w1, $w2
mov $w2, $w3
MEND
;使用这个宏
ldr r0, =1
ldr r1, =2
swap r0, r1, r2 ;此处调用了宏swap,运行完后r0、r1的值交换了
一般可以把宏写在宏文件(.mac文件)中,在程序里用INCLUDE指令包含宏文件
刚看到了cmp指令,一开始有点晕。后来上网找了些资料,终于看明白了,为了方便初学者,我就简单写下我的思路吧。高手绕过,谢谢! cmp(compare)指令进行比较两个操作数的大小 例:cmp oprd1,oprd2 为第一个操作减去第二个操作数, 但不影响第两个操作数的值 它影响flag的CF,ZF,OF,AF,PF 我们怎么判断大小呢? 若执行指令后 ZF=1 这个简单,则说明两个数相等,因为zero为1说明结果为0 当无符号时: 若 CF=1 则说明了有进位或借位,cmp是进行的减操作,故可以看出为借位,所以,此时oprd1<oprd2 CF=0 则说明了无借位,但此时要注意ZF是否为0,若为0,则说明结果不为0,故此时oprd1>oprd2 当有符号时: 若SF=0,OF=0 则说明了此时的值为正数,没有溢出,可以直观的看出,oprd1>oprd2 若SF=1,OF=0 则说明了此时的值为负数,没有溢出,则为oprd1<oprd2 若SF=0,OF=1 则说明了此时的值为正数,有溢出,可以看出oprd1<oprd2 若SF=1,OF=1则说明了此时的值为负数,有溢出,可以看出oprd1>oprd2 最后两个可以作出这种判断的原因是,溢出的本质问题: 两数同为正,相加,值为负,则说明溢出 两数同为负,相加,值为正,则说明溢出 故有,正正得负则溢出,负负得正则溢出 很简单的一条指令,初学者看看,我感觉我写的还挺详细的,哈哈……
补充: 两数相减,同号,则不溢出 两数为异号,结果与减数符号相同,则溢出。 |