文章目录
- 指令格式(重点)
- 1. 立即数
- 2. 寄存器位移
- 一、数据传送指令
- 1. MOV指令
- 2. MVN指令
- 3. LDR指令
- 二、数据计算指令
- 1. ADD指令
- 1. SUB指令
- 1. MUL指令
- 三、位运算指令
- 1. AND指令
- 2. ORR指令
- 3. EOR指令
- 4. BIC指令
- 四、比较指令
- 五、跳转指令
- 1. B/BL指令
- 2. ldr指令
- 练习
- 六、内存访问指令
- 1. 单内存访问指令
- 练习
- 2. 多内存访问指令
- 示例
- 3. 栈操作指令
- 示例
- 七、CPSR/SPSR操作指令
- 练习
- 八、ARM指令流水线分析及伪指令
- 1. 最佳流水线
- 2. 内存访问指令流水线
- 3. 分支流水线
- 4. ARM伪指令、汇编与C混合编程、Volatile关键字
- (1)LDR R0,=0x12345678分析
- (2)LDR R0,=Label 分析
- (3)LDR R0,Label
- (4)ADR R0,Label分析
- (5)如何判别代码在实际内存中运行的地址?
指令格式(重点)
1. 立即数
一个常数,该常数必须对应8位位图,即一个8位的常数通过,循环右移偶数位得到该数,该数
数为合法立即数。
在指令中表示方法:#数字,例如:#100
快速判定是否是合法立即数:
- 首先将这个数转换为32bit的16进制形式,例如
218
=0xDA
=0x000000DA
- 除零外,仅有一位数为合法立即数
- 除零外,仅有二位数,并且相邻(包括首尾,如
0x1000000A
)的为合法立即数。 - 除零外,仅有三位数,并且相邻(包括中间有0相间,例如
0x10800000
,包括首尾相邻
如:0x14000003
),这三位数中,最高位取值仅能为1、2、3,最低位取值仅能为4、8、C
中间位0x0~0xF。 这种组合的为合法立即数。
2. 寄存器位移
将寄存器值读取之后,进行移位运算后,作为操作数2
参与运算。支持的移位方式如下:
LSL(Logical shift Left)
逻辑右移LSR(Logical shift Right)
逻辑左移ASR(Arithmetic shift Right)
算术右移
r0,lsr #4 表示r0 >>4
r0,lsr r1 表示r0 >>r1
#3,LsL #4 错误,只能是寄存器移位,不能是立即数移位
一、数据传送指令
1. MOV指令
格式:mov
目标寄存器,操作数2
功能:将操作数2的值赋值给目标寄存器
2. MVN指令
格式:mvn
目标寄存器,操作数2
功能:将操作2取反的值给目标寄存器
3. LDR指令
格式: LDR
目标寄存器,= 数据
功能: 完成任意的数据传送到目标寄存器
注意: 数据前面不能加#,因为此时数据不按立即数来处理
二、数据计算指令
1. ADD指令
格式: add
目标寄存器,操作数1
,操作数2
功能: 将操作数1加上操作数2的结果给目标寄存器
1. SUB指令
格式: sub
目标寄存器,操作数1
,操作数2
功能: 将操作数1减去操作数2的结果给目标寄存器
1. MUL指令
格式: mul
目标寄存器,操作1
,操作2
功能: 将操作数1乘以操作数2的结果存放在目标寄存器
注意:操作数1
和操作2
必须都是寄存器,并且操作1
的寄存器编号不能和目标寄存器
一样
三、位运算指令
1. AND指令
格式: and
目标寄存器,操作数1
,操作数2
功能: 将操作数1按位与操作数2的结果存放在目标寄存器
2. ORR指令
格式: orr
目标寄存器,操作数1
,操作数2
功能: 将操作1按位或操作2的结果存放在目标寄存器
3. EOR指令
格式: eor
目标寄存器,操作1
,操作2
功能: 将操作数1按位异或操作数2的结果存放在目标寄存器
4. BIC指令
格式: bic
目标寄存器,操作1
,操作2
功能: 将操作数1按位与操作数2取反的结果存放在目标寄存器
目标寄存器 = 操作数1 & ~操作数2
四、比较指令
格式: cmp
寄存器,操作数2
等于寄存器
减去操作数2
功能: 将寄存器的值与操作2比较,比较的结果会自动影响CPSR的NZCV
答案
五、跳转指令
1. B/BL指令
格式: B
/BL
标签
功能: 跳到一个指定的标签,BL 跳转之前,将跳转前的PC的值保存在LR,跳转范围+/- 32M
NZCV 标志位
标志位 | 含义 |
---|---|
N (Negative) | 结果为负数(Rn < Rm) |
Z (Zero) | 结果为 0(Rn == Rm) |
C (Carry) | 发生借位(无符号比较时 Rn < Rm) |
V (Overflow) | 溢出(有符号计算超出范围) |
比较指令 + B 条件跳转
指令 | 条件 | 说明 |
---|---|---|
BEQ label | Z == 1 | 相等(Rn == Rm)时跳转 |
BNE label | Z == 0 | 不相等(Rn ≠ Rm)时跳转 |
BGT label | Z == 0 且 N == V | 大于(Rn > Rm,有符号)时跳转 |
BGE label | N == V | 大于等于(Rn ≥ Rm,有符号)时跳转 |
BLT label | N ≠ V | 小于(Rn < Rm,有符号)时跳转 |
BLE label | Z == 1 或 N ≠ V | 小于等于(Rn ≤ Rm,有符号)时跳转 |
BHI label | C == 1 且 Z == 0 | 大于(Rn > Rm,无符号)时跳转 |
BHS label | C == 1 | 大于等于(Rn ≥ Rm,无符号)时跳转 |
BLO label | C == 0 | 小于(Rn < Rm,无符号)时跳转 |
BLS label | C == 0 或 Z == 1 | 小于等于(Rn ≤ Rm,无符号)时跳转 |
2. ldr指令
格式: ldr
pc
,= 标签名
功能: 将PC指针指闻标签表示的地址
练习
答案
六、内存访问指令
1. 单内存访问指令
LDR
将内存中的值加载到寄存器(读内存)
STR
将寄存器的内容写入内存(写内存)
寄存器间接寻址:寄存器的值是一个地址
LDR ro,[r1 ] //r0 = *r1
STR ro,[ r1 ] //*r1 = ro
基址变址寻址:将基地址寄存器加上指令中给出的偏移量,得到数据存放的地址
- A. 前索引
STR r0,[r1,#4] //*(r1 + 4)= r0
LDR r0,[r1,#4] //r0 =*(r1+ 4)
- B. 后索引
STR r0,[r1],#4 //*r1=r0 &&r1=r1 + 4
LDR r0,[r1],#4 //r0=*r1 &&r1=r1 + 4
- C. 自动索引
STR r0,[r1,#4]! //*(r1+4)=r0&&r1=r1+4
LDR r0,[r1,#4]! //r0=*(r1+4)&&r1 =r1+4
示范:
练习
将1-10数据存放在基地址为0x4000,0000,将0x4000,0000起始地址的值拷贝到0x4000,0100
答案
将0x1234写到0x4000,0000,将0xabcd写到0x4000,0004,然后从这两个地址读取数据做案加,最终结果存放在r0
答案2
2. 多内存访问指令
LDM
将一块内存的数据,加载到多个寄存器中
STM
将多个寄存器的值,存储到一块内存
格式:
LDM
{条件}{s}<MODE
>基址寄存器
{!},{Reglist
}^
STM
{条件}{s}<MODE
>基址寄存器
{!},{Reglist
}^
mode | 说明 |
---|---|
IA | 后增加地址 |
IB | 先增加地址 |
DA | 后减少地址 |
DB | 先减少地址 |
基址寄存器
用于放内存的起始地址
!
最后更新基址寄存器的值
Reglist
- 多个寄存器,从小到大,中间用
,
隔开,如{r0,r2,r3}
或{r0-r7,r10}
- 寄存器号大的对应内存的高地址,寄存器号小的对应内存的低地址
^
- 它存在,如果
Reglist
没有pc
的时候,这个时候操作的寄存器是用户模式下的寄存器 - 在
LDM
指令中,有PC
的时候,在数据传送的时候,会将SPSR
的值拷贝到CPSR
,用于异常的返回
流程图:
示例
3. 栈操作指令
A. 进栈
stmfd sp!,{寄存器列表}
B. 出栈
Idmfd sp!,{寄存器列表}
注意
在对栈操作之前,必须先设置sp的值,进栈和出栈的方式一样,ATPCS标准规定满减栈
流程图:
堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈
堆栈指针指向下一个待压入数据的空位置,称为空堆栈
示例
七、CPSR/SPSR操作指令
A. 读操作
MRS Rn,CPSR/SPSR
将状态寄存器的值,读到通用寄存器中
B. 写操作
MSR CPSR/SPSR,Rn
将通用寄存器的值,写到状态寄存器
练习
A.写一段代码,将CPSR的第I(7)位清0,其他位不变(使能IRQ异常)
B.写一段代码,将CPSR的第I(7)位置1,其他位不变(禁用IRQ异常)
答案
八、ARM指令流水线分析及伪指令
在ARM核中,为增加处理器指令流的速度,ARM7系列使用3级流水线。允许多个操作同时处理,而非顺序执行。不同的ARM核,流水线的级数是不一样的,ARM核版本越高,流水线级数越多。对于软件工程师编程而言,统一按照三级流水线来分析就可以了。
PC指向正被取指的指令,而非正在执行的指令
1. 最佳流水线
该例中用5个时钟周期执行了5条指令,所有的操作都在寄存器中(单周期执行)
指令周期数(CPI)=1
2. 内存访问指令流水线
该例中,用6周期执行了4条指令,指令周期数(CPI)=1.5
3. 分支流水线
4. ARM伪指令、汇编与C混合编程、Volatile关键字
伪指令定义:
为了方便程序员使用,编译器设计的指令,这个指令ARM核无法直接识别,需要编译器对他翻译成ARM核所能识别的指令。
(1)LDR R0,=0x12345678分析
再次强调:PC指向正被取指的指令,而非正在执行的指令
如何看内存中的12345678
正在读取的LDR
内存是0x0008 加上 PC所在的地址(因为LDR正在执行 所以pc等于0x0000000C预取
的值)
也就是0x0008加上pc的值0x0000000C等于0x00000014
总结
编译器在编译的时候,将Idr r0,=0x12345678翻译成了ldr r0,[pc,#0x0008]这一条读内存的指令。根据PC的值加上偏移量算出0x12345678这个数据在内存的地址,然后使用Idr指令读取这个地址的数据。
(2)LDR R0,=Label 分析
1) 链接地址指定为0x0情况分析
0x00000018等于0x000C加上pc的值0x000C
注意 0x00000018的值是14 这是个值 是编译器算出来的一个值
2) 链接地址指定为0x2000情况分析
修改链接地址
再运行
label的地址也就是0x000c+pc的值0x0000200c=0x00002018
3) 总结
LDR r0,=Label指令表示将Label的值写入r0,Label的值由指定的代码段运行地址(-Ttext=地址值)来决定。
编译器做法:
- 首先根据指定的代码段开始的地址,算出Label标签对应的地址值
- 然后将这个表示的地址值存放在一个位置
- 生成内存访问指令,根据pc +固定偏移量,找到标签对应值存放的位置
注意
当代码编译结束的时候,标签表示的地址值(根据指定的代码段地址)已经编译死存放在程序文件中了。
(3)LDR R0,Label
LDR R0,Label 表示读取Label表示的地址对应数据
不带=
的时候 存的是标签里的内容
(4)ADR R0,Label分析
动态方式 根据pc的值+0x00000008
之前是静态的 在编译完的时候 label就已经确定值是什么了
这个是动态
举个例子:如果是用
LDR
我把这个代码放到A内存和B内存运行
这两块内存的值是一模一样的 因为在编译完的时候 label就已经确定值是什么了
如果是ADR
A内存的0x0008 和B内存的0x0008 是不一样的
有点难理解
ADR R0,Label指令表示根据当前的PC的值 +/-偏移量,动态获取当前Label所表示的内存地址
(5)如何判别代码在实际内存中运行的地址?
ADR r0,_start
可以知道,因为他是根据pc的值,动态获取
LDR r0,=_start
无法知道,这条指令不论在哪里运行,r0的值都是固定(取决于指定的链接地址)