- 一、数据处理指令
- 1)数学运算
- 数据运算指令的格式
- 数据搬移指令
- 立即数
- 伪指令
- 加法指令
- 带进位的加法指令
- 减法指令
- 带借位的减法指令
- 逆向减法指令
- 乘法指令
- 数据运算指令的扩展
- 2)逻辑运算
- 按位与指令
- 按位或指令
- 按位异或指令
- 左移指令
- 右移指令
- 位清零指令
- 3)比较指令
- 怎么影响到CPSR寄存器中的状(N, Z, C, V)
- 比较指令
- ARM指令的条件码
- 二、跳转指令
- 1)跳转指令
- 跳转指令
- 三、Load / Srore指令
- 1)内存访问指令
- 写内存
- 读内存
- 2)ARM指令的寻址方式
- 立即寻址
- 寄存器寻址
- 寄存器移位寻址
- 寄存器间接寻址
- 基址加变址寻址
- 基址加变址寻址的索引方式
- 多寄存器内存访问指令的寻址方式
- 3)栈的种类与使用
一、数据处理指令
数据处理指令(数学运算、逻辑运算)
1)数学运算
32位处理器什么意思:单次运算数据的能力,单次最大可处理32位的数据
数据运算指令的格式
《操作码》《目标寄存器》《第一操作寄存器》《第二操作数》
操作码: 表示执行哪种操作
目标寄存器: 表示存储运算的结果
第一操作寄存器: 存储第一个参与运算的寄存器(只能是寄存器)
第二操作数: 存储第二个参与运算的数据(寄存器、立即数都可以)
数据搬移指令
MOV R1, #1
MOV R2, R1 // R2 = R1
MOV PC, #7 // 可以更改PC的值,但是系统会默认把后两位改成0,7八进制>111系统会自动改成4>100
MVN R0, #0xFF // R0 = ~0xFF R0的内容就是0xFFFFFF00
立即数
立即数的本质是包含在指令当中的数,属于指令的一部分,这条执行是一起编译成机器码的
优点:
取值的时候就可以直接读取到CPU,不用单独去内存读取,速度快
缺点:
不能是任意的32位的数字,有局限性
MOV R1, #0x12345678 //不可以执行,因为数字太大
MOV R1, #0x12 //可以执行
伪指令
MOV R1, #0xFFFFFFFF
//当执行这条指令的时候显然这个数字太大,但是可以编译成功,是系统自动会将这条指令替换成 MVN R1, #0x00000000 这样就和MOV R1, #0xFFFFFFFF 指令执行的效果一样,这样的行为前提是两条指令要达到的效果相同
加法指令
ADD R1, R2, R3 //R1 = R2+R3
带进位的加法指令
ADC R5, R2, R4 // R5 = R2 + R4 + ‘CPSR->C’
1.编程实现使用32bit的ARM处理器实现两个128位的数据的加法运算。
注:
第一个数的bit[31:0]、bit[63:32]、bit[95:64]、bit[127:96]分别存储在R1、R2、R3、R4寄存器
第二个数的bit[31:0]、bit[63:32]、bit[95:64]、bit[127:96]分别存储在R5、R6、R7、R8寄存器
运算结果的bit[31:0]、bit[63:32]、bit[95:64]、bit[127:96]分别存储在R9、R10、R11、R12寄存器
@ 第一个数 0x00000004 00000002 FFFFFFFF 00000004
MOV R1, #0xFFFFFFFF
MOV R2, #0x00000002
MOV R3, #0xFFFFFFFF
MOV R4, #0x00000004
@ 第二个数 0x00000005 00000004 00000003 00000002
MOV R5, #0x00000002
MOV R6, #0x00000003
MOV R7, #0x00000004
MOV R8, #0x00000005
@ 运算结果
ADDS R9 , R1, R5 //加 'S' 可以影响到CPSR寄存器,高位运算时,可以看出低位有没有进位
ADCS R10, R2, R6
ADCS R11, R3, R7
ADC R12, R4, R8
减法指令
SUB R1, R2, R3 //R1 = R2-R3
带借位的减法指令
SBC R5, R2, R4 // R5 = R2 - R4 - ‘~CPSR->C’ 取反
逆向减法指令
RSB R1, R2, #3 //R1 = 3-R2
乘法指令
MUL R1, R2, R3 //R1 = R2*R3 乘法指令只能是两个寄存器执行
数据运算指令的扩展
MOV R1, R2, LSL #1 //R1 = (R2<<1)
2)逻辑运算
按位与指令
AND R1, R2, R3 //R1 = R2&R3
按位或指令
ORR R1, R2, R3 //R1 = R2 | R3
按位异或指令
EOR R1, R2, R3 //R1 = R2 ^ R3
左移指令
LSL R1, R2, R3 //R1 = R2 << R3
右移指令
LSR R1, R2, R3 //R1 = R2 >> R3
位清零指令
MOV R2, #0xFF
BIC R1, R2, #0x0F
//第二操作数的哪一位为1,就把第一寄存器中的哪一位清零,然后将结果放入目标寄存器中
3)比较指令
怎么影响到CPSR寄存器中的状(N, Z, C, V)
数据运算指令对条件位CPSR寄存器中的状(N, Z, C, V)的影响
默认情况下数据运算不会对条件位产生影响,当在指令后加后缀‘S’后可以影响
MOV R2, #3
SUBS R1, R2, #5 //将会对 N 状态位产生影响
比较指令
CMP R1, R2
本质是一条(SUBS),只是没有将运算结果放入寄存器当中,是看CSPR寄存器状态位(N, Z, C, V)
== Z=1
!= Z=0
< C=0
<= C=0 或 Z=1
> C=1 且 Z=0
>= C=1
MOV R1, #1
MOV R2, #2
CMP R1, R2
BEQ FUNC
@ 执行逻辑:if(EQ){B FUNC} 本质:if(Z==1){B FUNC}
BNE FUNC
@ 执行逻辑:if(NE){B FUNC} 本质:if(Z==0){B FUNC}
MOV R3, #3
MOV R4, #4
MOV R5, #5
FUNC:
MOV R6, #6
MOV R7, #7
@ ARM指令集中大多数指令都可以带条件码后缀
MOV R1, #1
MOV R2, #2
CMP R1, R2
MOVGT R3, #3
@ 练习:用汇编语言实现以下逻辑
int R1 = 9;
int R2 = 15;
START:
if(R1 == R2)
{
STOP();
}
else if(R1 > R2)
{
R1 = R1 - R2;
goto START;
}
else
{
R2 = R2 - R1;
goto START;
}
@ 练习答案:
MOV R1, #9
MOV R2, #15
START:
CMP R1,R2
BEQ STOP
SUBGT R1, R1, R2
SUBLT R2, R2, R1
B START
STOP:
B STOP
ARM指令的条件码
ARM指令集中大多数指令都可以带条件码后缀(如:SUBEQ)
二、跳转指令
1)跳转指令
跳转指令
有三种方式
第一种方法:直接去修改PC的值(不建议使用,因为需要自己计算绝对地址)
MAIN:
MOV R1, #1
MOV R2, #2
MOV PC, #0x16
MOV R3, #3
FUNC:
MOV R4, #4
MOV R5, #5
MOV R6, #6
第二种方法:不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下第一条指令的地址
MAIN:
MOV R1, #1
MOV R2, #2
B FUNC
MOV R3, #3
FUNC:
MOV R4, #4
MOV R5, #5
MOV R6, #6
第三种方法;带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下第一条指令的地址,同时将跳转指令的下一条指令的地址存储到LR
MAIN:
MOV R1, #1
MOV R2, #2
BL FUNC
MOV R3, #3
FUNC:
MOV R4, #4
MOV R5, #5
MOV R6, #6
MOV PC, LR
三、Load / Srore指令
Load / Srore指令(访问(读写)内存)
1)内存访问指令
Load/Srore指令:访问(读写)内存 当LD开头的指令 内存读数据到CPU 当ST开头的指令 把CPU中的数据存到内存当中
写内存
@ MOV R1, #0xFFFFFFF1
@ MOV R2, #0x40000000
@ STR R1, [R2] 默认是写入一个字(四个字节)的数据
@ STRB R1, [R2] 内存中写入'B'一个字节的数据'F1'
@ STRH R1, [R2] 内存中写入'H'两个字节的数据'FFF1'
@ R2->0x40000000内存空间 = R1的数据
读内存
@ MOV R1, #0xFFFFFFF1
@ MOV R2, #0x40000000
@ LDR R3, [R2] R3 = 默认内存读出一个字(四个字节)的数据
@ LDRB R3, [R2] R3 = 内存读出一个字节的数据
@ LDRH R3, [R2] R3 = 内存读出两个字节的数据
@ R3 = R2->0x40000000内存空间的数据
2)ARM指令的寻址方式
寻址方式就是CPU去寻找操作数的方式
立即寻址
@ MOV R1, #1
@ ADD R1, R2, #1
寄存器寻址
@ ADD R1, R2, R3
寄存器移位寻址
@ MOV R1, R2, LSL #1
寄存器间接寻址
@ STR R1, [R2]
基址加变址寻址
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ MOV R3, #4
@ STR R1, [R2,R3]
@ 将R1寄存器中的数据写入到R2+R3指向的内存空间
@ STR R1, [R2,R3,LSL #1]
@ 将R1寄存器中的数据写入到R2+(R3<<1)指向的内存空间
基址加变址寻址的索引方式
前索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]
@ 将R1寄存器中的数据写入到R2+8指向的内存空间
@ LDR R2, [R2,#8]
@ 将R2+8指向的内存空间的数据写入到R2寄存器中
后索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2],#8
@ 将R1寄存器中的数据写入到R2指向的内存空间,然后R2自增8
@ LDR R1, [R2],#8
@ 将R2+8指向的内存空间的数据写入到R2寄存器中,然后R1自增8
自动索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]!
@ 将R1寄存器中的数据写入到R2+8指向的内存空间,然后R2自增8
@ LDR R6, [R2,#8]!
@ 将R2+8指向的内存空间的数据写入到R6寄存器中,然后R6自增8
多寄存器内存访问指令的寻址方式
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMIA R11!,{R1-R4}
@ 先存储数据,后增长地址
@ STMIB R11!,{R1-R4}
@ 先增长地址,后存储数据
@ STMDA R11!,{R1-R4}
@ 先存储数据,后递减地址
@ STMDB R11!,{R1-R4}
@ 先递减地址,后存储数据
3)栈的种类与使用
栈的概念
栈的本质就是一段内存,程序运行时用于保存一些临时数据如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等
栈的分类
增栈:压栈时栈指针越来越大,出栈时栈指针越来越小
减栈:压栈时栈指针越来越大,出栈时栈指针越来越小
满栈:栈指针指向最后一次压入到栈中的数据,压栈时需要先移动栈指针到相邻位置然后再压栈
空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后需要将栈指针移动到相邻位置
栈分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种
ARM处理器一般使用满减栈
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV R4, #4
MOV R11,#0x40000020
STMFD R11!,{R1-R4}
LDMFD R11!,{R6-R9}
@ 结果 R6 = 1, R7 = 2, R8 = 3, R9 = 4
栈的应用举例
1.叶子函数的调用过程举例
@ 初始化栈指针
MOV SP, #0x40000020
MIAN:
MOV R1, #3
MOV R2, #5
BL FUNC
ADD R3, R1, R2
B STOP
FUNC:
@ 压栈保护现场
STMFD SP!, {R1,R2}
MOV R1, #10
MOV R2, #20
SUB R3, R2, R1
@ 出栈恢复现场
LDMFD SP!, {R1,R2}
MOV PC, LR
2.非叶子函数的调用过程举例
MOV SP, #0x40000020
MIAN:
MOV R1, #3
MOV R2, #5
BL FUNC1
ADD R3, R1, R2
B STOP
FUNC1:
STMFD SP!, {R1,R2,LR}
MOV R1, #10
MOV R2, #20
BL FUNC2
SUB R3, R2, R1
LDMFD SP!, {R1,R2,LR}
MOV PC, LR
FUNC2:
STMFD SP!, {R1,R2}
MOV R1, #7
MOV R2, #8
MUL R3, R1, R2
LDMFD SP!, {R1,R2}
MOV PC, LR
@ 执行叶子函数时不需要对LR压栈保护,执行非叶子函数时需要对LR压栈保护