目录
目录
arm汇编指令学习
基础概念
- c语言中可以那些代码可以生成汇编指令
1》带’;‘ 号的 语句 ,可以编译生成指令
2》带’#‘ 号 预处理 ,辅助编译器怎么编译,编译什么内容
- 汇编整体分类
1》指令: 编译完生成一条机器码存储在内存单元当中,CPU执行时能完成对应的操作(类似于C中的语句)
2》伪操作 (相当于c中的’#‘的内容)告诉编译器怎么编译):不会生成机器码也不会占用内存,其作用是告诉编译器怎样编译(类似于C中的预处理指令)
》伪指令 (如:cpu中没有乘法器,对应没有乘法指令,3*3 ---》用加法器实现3+3+3,替换实现):不是指令,编译器在编译时将其替换成等效的指令
汇编中注释代码用'@'注释一行 ,注释一段代码 /**/
- 指令分类
1.数据处理指令: 对数据进行逻辑、算数运算
2.跳转指令: 实现程序的跳转,实质是修改PC
3.Load/Store指令: 对内存的读写操作//如 a++ 读a的值,将运算结果从cpu写道内存
4.状态寄存器传送指: 对CPSR进行读写操作//其他都不能动CPSR
5.异常中断产生指令: 触发软中断,常用于内核的系统调用 //SWI:软中断
6.协处理器指令: 操作协处理器的指令
//如3*3 ---》用加法器实现3+3+3,比较慢。我们可以外接一个协处理器(乘法器)
(每个协处理器的功能比较单一),协处理器指令就是操作这个协处理器的,用的比较多的cp15协处理器。
- 汇编指令代码框架
.text @声明一段代码
.global _start @将_start 声明为一个全局的符号,其他.s文件也可以引用
@如调用函数 func : .global func
_start: @汇编的入口
@汇编代码段
.end @汇编的结束
汇编指令
- 指令的语法格式:
- <opcode><code>{s} Rd, Rn, oprand2
- opcode:指令的名字
- code:条件码(if else),可以省略不写,默认指令是无条件执行
- s:状态标志
加s,指令的执行结果影响cpsr的NZCV位,
不加s,不影响
- Rd:目标寄存器
- Rn:第一个操作寄存器
- oprand2:第二个操作数,可以是普通寄存器,可以是立即数
- 注:指令的名字,条件码,s连到一起写,指令名和目标寄存器之间使用空格,寄存器和数据之间使用逗号隔开,指令中的字符不区分大小写
数据处理指令
- 1》数据搬移指令 mov
- @ 格式:<opcode><code>{s} Rd, oprand2
- 如果是立即数,前边必须加#
- PC寄存器详细讲解:
- 指令的执行三步:取地,译码,执行(PC永远指向当前正在取指指令的地址)
2》立即数:立即数是保存在指令中的数,取指令的同时将值取过去,和普通变量的区别是,变量保存在内存中的数据,需要单独取值运算。
立即数的本质:立即数是包含在指令当中的数据(即属于指令的一部分)
立即数的优点:读取指令的同时也将立即数读取到了内存中,速度快
立即数的缺点:数量有限
如:MOV ,#0x12345678 @报错,不合法
怎么理解组合:就是把这个数写成二进制然后把第一个1出现的位置与最后一个1出现的位置框起来
注:使用mov 给寄存器里面存放值的时候,#号后面需是有效数(1:立即数,2:取反之后是立即数),如果不是立即数需要用ldr指令进行存放。
如果不是立即数,用伪指令ldr 赋值
直接把0下2345678值放到r0的地址中
- 3》算数运算指令
算数运算指令 add adc sub sbc mul
数据运算指令格式s
<操作码><目标寄存器><第一操作寄存器><第二操作数>
ADD R3,R1,R2 ;R2可以是立即数 只有乘法这不能为立即数
;操作码 指定当前指令是哪种运算
;目标寄存器 存放运算结果
;第一操作寄存器 存放参与运算的一个数据(只能是寄存器)
;第二操作数 存放参与运算的另一个数据(可以是寄存器/立即数)
- add 普通的加法指令
- adc 带进位的加法指令
假设2个64位的数相加
- 第一个64位的数,R0存放低32位,R1存放高32位,
- 第二个64位的数,R2存放低32位,R3存放高32位
- 结果R4存放低32位,R5存放高32位
- 因为低八个字节有进位而你用add 是不支持进位的 需要用adc支持进位
- 你用了adc也没有进位那是因为 因为没有更新状态位需要把add改成adds就变成了如果有进位就更新状态位 如下图所示
- 高位的add加上s就会更新CPSR进位状态值
- sbc 带进位的加法指令
总结进位借位:
进位:低八位要进位就写成adds 高八位被进位写adc支持进位指令
借位:低八位要借位就写成subs 高八位被借位写成sbc
- 注意:mul r2, r0, #0x4 @ 错误
乘法指令的第二个操作数只能是一个寄存器 不能写一个常量值
mul r2, r1,r0 @正确
跳转指令
1》修改PC,不建议使用,因为需要查询指令的地址
2》 b bl :指令跳转
- 格式:b/bl Label
- Label: 指令
- 相当C语言的函数调用
- B指令(不带返回的跳转)
不保存返回地址的跳转(返回地址不保存到lr中)
- BL指令(带返回的跳转指令),将LR的值修改成跳转指令下一条指令的地址,再将PC的值修改成跳转标识符下指令的地址
补充了解:
RM指令条件码表:可跟的判断条件成立跳转(NZCV在用于判断两者之间关系使用比较多)
如:c代码如下:
练习:
实现以下逻辑
unsigned int r1 = 9;
unsigned int r2 = 15;
while(1)
{
if(r1 == r2)
goto stop;
if(r1 > r2)
r1 = r1 - r2;
if(r1 < r2)
r2 = r2 - r1;
}
stop:
while(1);
汇编指令练习答案如下:
mov r1,#9
mov r2,#15
loop:
cmp r1,r2 @cmp 比较指令
beq stop
subhi r1,r1,r2
subcc r2,r2,r1
b loop
stop:
b stop
Load/Store指令
对内存的读写操作//如 a++ 读a的值,将运算结果从cpu写道内存
可用地址查找:(我们不用查找,脚本文件中配置了内存空间的分配)
查看内存中内容:
上图含义:就是r1里面的数据写道地址为0x40000000的地方
1>单寄存器操作指令 ldr/str
格式:ldr/str Rm, [Rn]
Rm: 存储是数据
Rn:存储的数据,地址
将CPU中r1寄存器中的数据存储到内存中r0地址的空间中
存储的方式是小端存储 就是低地址存放低字节
str是r1里面的数据写道r0保存的地址里面
ldr是把r0地址里面保存的内容读到r3里面
- 将r0指向的地址空间中的内容,读到r2寄存器中
- ldr r2, [r0]
- 将r1中的值存储到r0+4指向的地址空间中,R0中的值不变
- str r1, [r0, #4];
- 将r2中的值存储到r0指向的地址空间中,r0 = r0 + 4
- str r2, [r0], #4 解释先把r2的数据放到r0地址里面然后r0的地址在移动四个字节
- 将R3中的值存储到R0+4指向的地址空间中,并且r0 = r0 + 4
- str r3, [r0, #4]! 解释 这个r3的数据写到r0加4的地址 并且r0的地址也增加4
-
2>多寄存器操作指令 stm ldm
- 将r1到r4中的值存储到r0指向地址空间中,连续16个字节的地址空间
- stm r0, {r1-r4}
- 将r0指向的地址空间中,连续的16个字节的数据,读到r5-r8寄存器中
- ldm r0, {r5-r8}
- 如果寄存器列表中的寄存器编号既有连续又有不连续,连续的使用“-”隔开,不连续的使用“,”
- stm r0, {r1-r3,r4}
- 2. 不管寄存器列表中的寄存器编号顺序如何变化,都是小地址对应小编号的寄存器高地址对应大编号的寄存器
- stm r0, {r4,r3,r2,r1}
- ldm r0, {r8,r7,r6,r5}
加不加感叹号的区别 读的话r0会移动到读的首地址
-
3>栈的操作指令 stmfd ldmfd
- 栈的种类
- 空栈(Empty)
栈指针指向的地址是空的,在栈中存储数据时,可以直接存储,存储完成之后需要将栈指针再次指向空的位置。
-
- 满栈(Full)
栈指针指向的地址有数据,在栈中存储数据时,需要先将栈指针,指向一个空的位置,然后在存储数据。
-
- 增栈(Ascending)
栈指针向高地址方向移动
-
- 减栈(Descending)
栈指针向低地址方向移动
操作栈的方式有四种
- 满增栈 满减栈 空增栈 空减栈
- FA:Full Ascending 满增(FA)
- FD:Full Descending 满减(FD)
- EA:Empty Ascending 空增(EA)
- ED:空减
- ARM默认采用的是满减栈
- stmfd/ldmfd<code> sp!, {寄存器列表}
- stmfd sp!, {r1-r5}(写) (压栈)
更新栈指针指向的地址空间
- ldmfd sp!, {r6-r10}(读) (出栈)
特殊:
stmfd sp!, {r1-r5,lr}(写) (压栈)
ldmfd sp!, {r6-r10,pc}(读) (出栈) //r1-r5出栈给r6-r10, 将lr的值出栈给pc