ARM指令
影响CPSR的三种终极情形:
"目标寄存器":暂存运算结果的寄存器,离指令最近的寄存器
1.指令后面加s,并且目标寄存器是pc,不仅仅实现一个跳转
还要实现一个状态的恢复(cpsr=spsr)
movs pc, lr 此指令做两件事:
1)pc=lr,结果实现一个返回跳转
2)cpsr=spsr,结果实现一个状态的恢复
2.指令后面加s,但是目标寄存器不是pc, 根据运算结果影响cpsr的nzcv位
subs r0, r1, #1 @r0=r1-1,根据运算结果r0里的值影响cpsr的nzcv位
3.比较测试指令后面不加s,运算结果照样影响cpsr的nzcv位!
cmp r0, #1 @本质做:运算结果=r0-1,结果影响cpsr的nzcv位
1.数据传输指令
1)MOV
Mov和mvn指令操作的立即数的范围是0x00~0xFF
MOV(MOVE)指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄器
例如:
mov r0, #0x1F @合法
mov r0, #0x1FF @不合法
mov R0,R1 @把R1的值传到R0
Mov R3,#3 @把常数3传给R3,MOV中用#表示常数,这个值不能超过
MOVS PC,LR @把LR的值给PC实现跳转返回,由于目标寄存器是PC且指令后面加S,实现状态恢复(cpsr=spsr)
2)MVN
MVN( MOVE Negative)按位取反后再传值,比MOV多了一步取反
mvn r0, #0x0 @合法,结果:r0=0xFFFFFFFF
mvn R0, #0 @把0取反(即-1)传给R0
mvn R1,R2 @把R2的值按位取反传给R1
mvn R3,R4,LSL,#2 @把R4的值左移两位后按位取反后给R3
2.算数运算指令
1)ADD/ADC
add加法指令
add R0,R1,R2@R0=R1+R2
add R0,R1,#3 @R0=R1+3
add r0, r0, #1 @r0=r0+1
adc带进位加法指令,即除了加两个数以外,还要把CPSR的C值也要带进来
adc r0, r0, #1 @r0=r0+1+cpsr的c位
通常用于大数(超过32Bit整数)相加,这时单用ADD不能处理,必须折成两步,其中一步用ADC.
以下是做64Bit的加法
ADDS R0,R1,R2@R0=R1+R2,ADDS中S表示把进位结果写入CPSR
ADC R5,R3,R4 @R5=R3+R4+C
2)SUB/SBC
sub减法指令
SUB R0,R1,R2@R0=R1-R2
SUB R0,R1,#3 @R0=R1-3
sub r0, r0, #1 @r0=r0-1
sbc带进位减法指令,即除了加两个数以外,还要把CPSR的C值也要带进来,类似adc
以下是做64Bit的减法
SUBS R0,R1,R2@R0=R1-R2,SUBS中S表示把进位结果写入CPSR
SBC R5,R3,R4 @R5=R3-R4-cpsr的c位
3)MUL:MUL 乘法指令
MUL R0,R1,R2@R0=R1*R2
MUL R0,R1,#3 @R0=R1*3
3.位运算指令
1)AND:AND位与指令
AND R0,R1,R2@ R0=R1 & R2
AND R0,R1,#0xFF @R0=R1 & 0xFF
AND R0,R1,R2,LSL,#1 @R2左移一位,再跟R1按位与,把结果给R0,结果影响cpsr的nzcv
2)ORR:ORR位或指令
ORR R0,R1,R2@ R0=R1 | R2
ORR R0,R1,#0xFF @R0=R1 | 0xFF
3)EOR:EOR位异或指令
EOR R0, R0, #3@ 反转R0中的位0和1
EOR R1,R1,#0x0F@将R1的低4位取反
EOR R2,R1,R0@R2=R1^R0
EORS R0,R5,#0x01 @R5和0x01进行逻辑异或,结果保存到R0,结果影响cpsr的nzcv
4)BIC:BIC先取反后位与 (&= ~),清位操作
BIC R0,R0,#0xF @ 等同于先把0xF取反然后跟R0按位与,把结果放到R0中去
BIC R0,R0,#%1011 @等同于先把1011取反然后跟R0按位与,把结果给R0,该指令清除 R0 中的位 0 1 3,其余的位保持; %表示是二进制,0x表示十六进制
BICEQS PC,R0,#0X7F @因为指令带EQ,所以首先判断当前CPSR的Z位,为1执行,为0不执行,然后将0x7f按位取反后跟R0按位与,把结果给PC,因为指令后面加S,且目标寄存器是PC,所以实现状态恢复CPSR=SPSR
4.比较测试指令(这些指令后面不加s,运算结果照样影响cpsr的nzcv位)
1)CMP:比较指令
CMP比较两个操作数大小(减法),并把结果存入CPSR,供下一句语句使用
CMP R0,R1 @比较R0,R1大小
CMP R1,#10 @假设R1的值是10,同10做减法运算=10,影响Z位,Z位置1,MOVEQ先判断Z位,条件成立,执行
MOV,把R1的值给R0,因为指令后面没有加S,所以不影响标志位,Z位还是1,BLEQ判断Z位为1,
执行BL,跳转到test,在这之前将下一条指令保存到LR里
MOVEQ R0,R1
BLEQ test @如果R1不等于10,MOVEQ和BLEQ不执行
……
test:
…….
2)CMN:反值比较指令
CMN比较取负的值(加法),并把结果存入CPSR,供下一句使用
CMN R0,#1 @把R0与-1进行比较
CMN R1,#5 @假设R1=-5,将-5和5做加法运算,结果为0,Z位置1,BEQ先判断Z位,Z位为1,执行B,实现跳转,执行SKIP
BEQ SKIP
…….
SKIP:
……
3)TST:位测试指令
TST 测试某一位是否为1(位与),并把结果写入CPSR,供下一句使用
与AND的区别是,TST不加S就影响CPSR
TST R1,#0xffe @等同于if(R1 & 0xffe),结果影响NZC位(TST没有溢出,不影响V位)
TST R1,#%1 @测试最低位是否为1,%表示二进制
TST R0,#0x1 @R0跟0x1做位与,用来判断R0的第0位是不是0,如果是0则影响Z位,Z=1,从而影响下面语句的实现,
ADDEQ R1,R2,R3
4)TEQ:相等测试指令
TEQ 对两个操作数进行位异或操作,并把结果写入CPSR,供下一句使用
TEQ R0,R1@比较R0和R1是否相等
5.加载存储指令(LDR,STR是用于寄存器和外部存储器交换数据指令(注意与MOV的区别,后面只在寄存器或常数交换))
任何数据的运算都必须进过CPU核的处理
“加载”:将外设的数据读取到CPU核内部的寄存器中,对应的指令为ldr
“存储”:将CPU核内部寄存器中的数据写入到外设中,对应的指令为str
切记:CPU核运算的数据来自外设,经过CPU核运算最终还要回归到外设中,否则运算毫无意义。
1)LDR(ldr(4字节)/ldrb(1字节)/ldrh(2字节)/ldrsb(有符号1字节)/ldrsh(有符号2字节))
LDR(load)用于把一个32Bit的WORD(四字节)数据从外部存储空间读取到CPU核内部寄存器中
LDR R0,[R1] @以R1寄存器的中的数据当做地址,取出4字节数据放到ARM核R0这个寄存器中 (R0=*R1)
LDR R0,[R1, #8] @将(R1的值 + 8)当成地址,把这地址里的四字节数据装入R0存储器中(R0=*(R1+8))
LDR R0,[R1,#0x8]! @先将R1的值加+8当成地址,把这个地址里的数据装入到R0里面,并将R1的值更新,即R1=R1+8
LDR R0, [R1], #8 @将R1的值当成地址,再把这个地址里的数据读入到R0,并将R1 + 8的值存入R1
2)STR((4字节)/strb(1字节)/strh(2字节))
STR R0,[R1] @将ARM核R0中的数据写入到R1存储的数据为地址的存储空间中
STR R0, [R1, #12] @将R0里面的值放到R1的值+12为地址的存储空间中
STR R0,[R1,#12]! @将R0里面的值放到R1的值+12为地址的存储空间中,然后更新R1,即R1=R1+12
STR R0,[R1],#12 @将R0里面的值放到R1的值作为地址的存储空间中,然后更新R1,即R1=R1+12
参考代码:
MOV R0,#0x48000000 @假设成立,R0=0x48000000,而0x48000000对应的是内存中的某个地址
LDR R1,[R0] @以R0寄存器中的数据为地址,取出4字节数据放到ARM核R1这个寄存器中,也就是从内存
的0x48000000地址取出4字节数据放到ARM核R1寄存器中
ADD R1,R1,#1 @将从内存读取的数据在CPU核内部完成一个数据运算(加法运算)
STR R1,[R0] @将ARM核R1寄存器中的数据写入到以R0寄存器中存储的数据为地址的存储空间中,也就
是将ARM核R1中的数据存储到内存的0x48000000这个地址存储空间上
6.栈操作指令
Linux系统采用的是满减栈(先调整SP后压入数据,sp指针向内存地址减小的方向变化)
ARMV7之前的ARM核采用老版本的满减栈栈操作指令:
STMFD:压栈指令,就是将ARM核中的数据保存到栈中
STMFD SP! {R0-R4,LR} @将ARM核中R0,R1,R2,R3,R4和LR寄存器中的数据保存到栈中,更新SP
LDMFD:出栈指令,就是将栈中的数据恢复到ARM核寄存器中
LDMFD SP! {RO-R4,PC} @从栈中将之前保存的数据恢复到ARM核中的R0,R1,R2,R3,R4和PC寄存器中,更新SP
ARMV7之后采用新的ARM满减栈操作指令:
PUSH:压栈指令,就是将ARM核中的数据保存到栈中
PUSH SP! {R0-R4,LR} @将ARM核中R0,R1,R2,R3,R4和LR寄存器中的数据保存到栈中,更新SP
POP:出栈指令,就是将栈中的数据恢复到ARM核寄存器中
POP SP! {RO-R4,PC} @从栈中将之前保存的数据恢复到ARM核中的R0,R1,R2,R3,R4和PC,更新SP
入栈从右往左开始
出栈先取数后调整SP,先进后出
7.跳转操作指令
ARM指令之分支跳转指令:b/bl
b:不带返回的跳转(一去不复返)
bl:带返回的跳转
切记:当CPU执行bl指令时,CPU核硬件上自动将下一条指令的地址保存
到lr寄存器中,将来返回只需调用:mov pc, lr
参考代码
地址 指令
0x0 bl xxx @CPU核执行时,lr=0x4
0x4 sub
0x8 and
... ... @注意:这些指令执行完毕以后,继续向下执行
xxx:
add ...
sub ...
mov pc, lr @pc=lr=0x04,让CPU核跑到0x04去运行,返回到上一个sub运行
8.移位操作指令
LSL:左移,低位补0
LSR:右移,高位补0
ASR:右移,高位补符号位
ROR:循环右移
RRX:右移,高位补C(CPSR的C位)
9.ARM伪指令
ARM伪指令不属于ARM指令集中的指令
定义这些指令可以使ARM汇编程序设计变得更方便
汇编器会自动把一条或多条ARM指令替换ARM伪指令
1)伪指令值ADR
ADR伪指令用于加载地址
注意:相对PC,向前最多加载1020(255X4)字节,,向后最多加载1020(255X4)字节
ARD伪指令的真实指令是ADD
例如:
ADR R0,Delay @将Delay标签的地址放到R0寄存器中
…. @这些代码的数量不能超过255条指令
Delay: @Delay标签地址等于MOV R0,#10这条指令的地址
MOV R0,#10
反汇编查看ARD的真实指令
.text
.code 32
.global _start
_start:
adr r0, Delay
mov r1, #1
mov r2, #2
mov r3, #3
Delay:
mov r4, #4
mov r5, #5
.end
得到:
Disassembly of section .text: //adr.o代码段的内容如下
00000000 <_start>: //入口函数_start标签的地址为0x00000000
0: e28f0008 add r0, pc, #8
4: e3a01001 mov r1, #1
8: e3a02002 mov r2, #2
c: e3a03003 mov r3, #3
00000010 <Delay>: //Delay标签函数对应的地址为0x00000010
10: e3a04004 mov r4, #4
14: e3a05005 mov r5, #5
Disassembly of section .ARM.attributes: //ARM程序其余属性段相关内容
... //汇编器arm...as额外添加的段
只需分析代码段的内容即可,说明如下:
1.第一列表示指令对应的地址,一条指令4字节
2.第二列表示指令对应的机器码,地址中存储的就是机器码
机器码最终给CPU用
3.第三列就是机器码的注释说明[就是指令],给程序员看
结论:
1.adr最终真实的指令是add指令
2.当add执行的时候,也就是adr执行,执行的结果是add r0,pc,#12
@pc=0x08,r0=0x80+8=0x10
2)伪指令之LDR
LDR作为伪指令的使用形式有三种:
形式1:
mov r0, #0x48000000 @不合法
ldr r0, =0x48000000 @合法,结果:r0=0x48000000,ldr作为伪指令对立即数的范围无要求
形式2:
ldr r0,=testdata @此ldr作为伪指令,将testdata标签保存的地址给r0,也就是将分配的字节的内存空间的首
地址给r0
ldr r1,[r0] @此ldr作为真实指令,以r0保存的值为地址取出4字节数据给r1
testdata: @类似C程序 的指针变量,保存分配的4字节内存空间的首地址
.int 0x12345678 @分配4字节的内存空间,并且初始化为0x1235687
形式3:l
dr r0,testdata @此ldr作为伪指令,将testdata标签保存的地址给r0,也就是将分配的字节的内存空间的
首地址给r0
…….
testdata: @类似C程序 的指针变量,保存分配的4字节内存空间的首地址
.int 0x12345678 @分配4字节的内存空间,并且初始化为0x1235687
经典代码:
ldr pc,jump_table @将jmp_table的标签内容给pc,结果是pc=main,让cpu核跑到main函数中运行
jmp_table:
.int main @分配四字节内存空间,保存main函数的地址
3)NOP伪指令
NOP伪指令在汇编时将会被替代成ARM中的空操作,比如可能是“MOV R0,R0”指令等。NOP可用于延时操作
10.ARM伪操作
伪操作(.开头)不参与程序的运行,起到一个标识的作用,共121个
——常量定义伪操作
——符号声明伪操作
——数据定义伪操作
——汇编控制伪操作
——信息控制伪操作
——其他伪操作
例如:
.equ PI, 3.14 @类似:#define PI (3.14)
.byte 250 分配1字节内存空间并且初始化为250
.space 4096 分配4096字节的内存空间
.skip 4096 分配4096字节的内存空间
.ascii "hello,world\0" 分配内存空间并且初始化为"hello,world"
.asciz "hello,world" 分配内存空间并且初始化为"hello,world"
问:
.byte/.short/.hword/.int/.word/.long/.space/
.skip/.ascii/.asciz/.string/他们都是用来分配内存
汇编程序如何获取到他们分配内存空间的首地址呢?
只有将来获取首地址汇编程序就可以随意访问了
答:只需给这些伪操作添加一个标签即可
切记:标签的地址就是分配的内存空间的首地址
str1:
.asciz "hello"
str2:
.asciz "world"
mem_sapce:
.space 4096
案例:利用汇编实现两个字符串的比较
回顾:C实现字符串比较
int my_strcmp(const char *str1, const char *str2)
{
while(*str1) {
if(*str1 != *str2)
return *str1 - *str2;
str1++;
str2++;
}
return *str1 - *str2;
}
汇编实现字符串比较函数的流程:
.text
.arm @等价于.code 32
.global _start
_start:
ldr r0, =str1 @r0指向“hello”
ldr r1, =str2 @r1指向“hfflo”
loop: @字符串都是存于内存,将来运算必须在CPU核内部
ldrb r2, [r0], #1 @取出一个字符
ldrb r3, [r1], #1 @取出一个字符
cmp r2, #0
beq loop_end
cmp r2, r3
beq loop
loop_end:
sub r0, r2, r3
b .
str1:@str1指向"hello",字符串都是存在于内存中
.asciz "hello"
str2:@str2指向"hfllo",字符串都是存于内存中
.asciz "hfllo"
.end
最后观察r0值:
=0:表示相等
>0:表示str1>str2
<0:表示str1<str2