ARM汇编指令学习
- 工程搭建
汇编编程环境搭建
HN0AE-522LW-NNRAF-63PUS-7IGFH-YF58D
配置编译工具链
为工程配置链接脚本(map.lds)
将map.lds 复制到工程文件夹(在桌面创建的ARM-ASM文件)
创建汇编文件
接下来我们需要建立一个start.s汇编文件添加到我们的工程中去
- 汇编指令学习
c语言中哪些代码可以生成汇编指令?
1》带#号预处理,辅助编译器怎么编译,编译什么内容
预处理器是C语言编译器的一个组成部分,它在编译代码之前对代码进行处理。预处理器指令以#号开头,告诉编译器在编译代码之前执行一些操作。其中,#include指令用于将头文件包含到源代码中,#define指令用于定义宏。预处理器的主要作用是辅助编译器编译代码。 例如在编译时将头文件中的函数声明插入到源代码中,或者将宏替换为实际的值。预处理器处理完代码后,编译器将生成目标代码,最终生成可执行文件。
2》带;号的语句,可以编译生成指令
在编译器中,分号是语句结束的标志,编译器会将分号之前的语句编译成指令并添加到指令序列中。
汇编整体分类
1》指令:编译完生成一条机器码存储在内存单元当中,CPU执行时能完成对应的操作(类似于C中的语句)
2》伪操作(相当于c中的#的内容,告诉编译器怎么编译):不会生成机器码也不会占用内存,其作用是告诉编译器怎样编译(类似于C中的预处理指令)
3》伪指令:不是指令,编译器在编译时将其替换成等效的指令
(如:cpu中没有乘法器,对应没有乘法指令,3*3 ---》用加法器实现3+3+3,替换实现)
汇编中注释代码用@或;注释一行 ,/* */注释一段代码
指令分类
1.数据处理指令: 对数据进行逻辑、算术运算
2.跳转指令: 实现程序的跳转,本质是修改PC
3.Load/Store指令: 对内存的读写操作
4.状态寄存器传送指令: 对CPSR进行读写操作
5.异常中断产生指令: 触发软中断,常用于内核的系统调用 //SWI:软中断
6.协处理器指令: 操作协处理器的指令
//如3*3 ---》用加法器实现3+3+3,比较慢。我们可以外接一个协处理器(乘法器)(每个协处理器的功能比较单一),协处理器指令就是操作这个协处理器的,用的比较多的cp15协处理器。
汇编指令代码框架
.text @声明一段代码
.global _start @将_start 声明为一个全局的符号,其他.s文件也可以引用
@如调用函数 func : .global func
_start: @汇编的入口
@汇编代码段
.end @汇编的结束
(最后空一行,否则有警告)
指令的语法格式(不用记)
<opcode{<cond>}{S}> <Rd>, <Rn>, <Operand2>
<操作码> <目标寄存器Rd> <第一操作寄存器Rn> <第二操作数Operand2>
;第一个位置必须是寄存器,第二操作数可以是寄存器,也可以是立即数
<opcode{<cond>}{S}> <Rd>, <Rn>, <Operand2>
opcode:指令的名字
cond:条件码(if else),可以省略不写,默认指令是无条件执行
S:状态标志
加s,指令的执行结果影响CPSR的NZCV位,
不加s,无影响
Rd:目标寄存器
Rn:第一个操作寄存器
oprand2:第二个操作数,可以是普通寄存器,可以是立即数
注:指令的名字,条件码,s连到一起写,指令名和目标寄存器之间使用空格,寄存器和数据之间使用逗号隔开,指令中的字符不区分大小写
- 数据处理指令
数据搬移指令 mov
如果是立即数,前面必须加#号
什么是立即数?
立即数
立即数通常是指在立即寻址方式指令中给出的数。可以是8位、16位或32位,该数值紧跟在操作码之后。
寻址方式:
寻址方式_百度百科
立即数是保存在指令中的数,取指令的同时将值取过去,和普通变量的区别是,变量保存在内存中的数据,需要单独取值运算。
立即数的本质:立即数是包含在指令当中的数据(即属于指令的一部分)
立即数的优点:读取指令的同时也将立即数读取到了内存中,速度快
立即数的缺点:数量有限
怎么判断一个数是否立即数?
给定一个数,将这个数中的所有的1,可以组合成一个0-255之间的数,将0-255之间的这个数,循环右移偶数个位数,如果可以得到给定的这个数,说明是立即数。
问:只要挨着的就是立即数?对吗?(对)
注:使用mov 给寄存器里面存放值的时候,#号后面需是有效数(1:立即数,2:取反之后是立即数),如果不是立即数需要用ldr指令进行存放。
如果不是立即数,用伪指令ldr 赋值
PC寄存器讲解
指令的执行三步:取址,译码,执行(PC永远指向当前正在取指指令的地址)
算术运算指令
数据运算指令格式
<操作码><目标寄存器><第一操作寄存器><第二操作数>
ADD R3,R1,R2;
操作码 指定当前指令是哪种运算
目标寄存器 存放运算结果
第一操作寄存器 存放参与运算的一个数据(只能是寄存器)
第二操作数 存放参与运算的另一个数据(可以是寄存器/立即数)
算术运算指令
add加法 adc 带进位的加法
sub 减法 sbc 带借位的减法
mul乘法 (乘法运算的R2(第二操作数)不能为立即数)
add 普通加法
adc 带进位加法
假设有两个64位的数相加
第一个64位数,R0放低32位数,R1放高32位数;
第一个64位数,R2放低32位数,R3放高32位数;
结果放在R4放低32位数,R5放高32位数;
减法运算,产生借位时c=1,否则 c=0;
sub 普通减法
subs 减法(刷新CPSR)
减法指令执行时,有借位时 CPSR 'C' 位置 1
mul 乘法
注意:mul r4, r3, #0x4 @ 错误------>乘法指令的第二个操作数只能是一个寄存器
与 and 、或orr、 异或eor、 左移lsl、 右移lsr
mov r0,#1
mov r1,#2
mul r2,r1,r0
and r3,r1,#1 @与 R3 = R1&1 -->0
orr r4,r2,r1 @或 R4 = R2|R1-->2
eor r5,r2,r1 @异或 R5 = R2^R1-->0
lsl r6,r2,r1 @左移 R6 = R2<<R1-->8
lsr r7,r2,r1 @右移 R7 = R2>>R1-->0
- 跳转指令
实现程序的跳转,本质是更改PC
修改PC
不建议使用,因为需要查询地址
b bl :指令跳转
格式:b/bl Label
Label: 指令
相当C语言的函数调用
b指令(不带返回的跳转)
不保存返回地址的跳转(返回地址不保存到lr中)
一路向前,不返回
bl指令(带返回的跳转指令)
将LR的值修改成跳转指令下一条指令的地址
再将PC的值修改成跳转标识符下指令的地址
补充了解:
指令条件码表:可跟的判断条件成立跳转(NZCV在用于判断两者之间关系使用比较多)
练习
实现以下逻辑
unsigned int r1 = 9;
unsigned int r2 = 15;
while(1)
{
if(r1 == r2) //cmp
goto stop;
if(r1 > r2)
r1 = r1 - r2;
if(r1 < r2)
r2 = r2 - r1; //subcc r2,r2,r1
}
stop:
while(1);
- load/store指令(批量操作)
对内存的读写操作,将运算结果从cpu写到内存
可用地址查找:(我们不用查找,脚本文件中配置了内存空间的分配)
单寄存器操作指令 ldr / str
- 格式:ldr/str Rm, [Rn]
ldr:读
str:写
Rm: 存储是数据
Rn:存储的数据,地址
1>前索引
mov r1,#0xffffffff
mov r2,#0x40000000
str r1,[r2,#8] @基址加变址寻址把R1存在0x40000000+8的内存里
2>后索引
mov r1,#0xffffffff
mov r2,#0x40000000
str r1,[r2],#4 @将R1寄存器的内容存到[R2]地址,然后R2=R2+4
目的:可以做连续存储,压栈时用的比较多 存完一个数,他就把地址自动指向下一个了
3>自动索引(前后索引)
mov r1,#0xffffffff
mov r2,#0x40000000
str r1,[r2,#4]! @将R1寄存器的内容存到[R2+4]地址,然后R2=R2+4
批量寄存器操作指令ldm/stm
1、将r1到r4中的值存储到r0指向地址空间中,连续16个字节的地址空间
stm r0, {r1-r4}
2、将r0指向的地址空间中,连续的16个字节的数据,读到r5-r8寄存器中
ldm r0, {r5-r8}
3、如果寄存器列表中的寄存器编号既有连续又有不连续,连续的使用 - 隔开 不连续的使用 ,
stm r0, {r1-r3,r5}
4、不管寄存器列表中的寄存器编号顺序如何变化,都是小地址对应小编号的寄存器高地址对应大编号的寄存器
stm r0,{r4,r2,r1,r3}
ldm r0,{r8,r6,r5,r7}
4、栈的操作指令 stmfd / ldmfd
栈的种类
空栈(Empty)
栈指针指向的地址是空的,在栈中存储数据时,可以直接存储,存储完成之后需要将栈指针再次指向空的位置。
满栈(Full)
栈指针指向的地址有数据,在栈中存储数据时,需要先将栈指针,指向一个空的位置,然后在存储数据。
增栈(Ascending)
栈指针向高地址方向移动
减栈(Descending)
栈指针向低地址方向移动
操作栈的方式
满增栈,满减栈,空增栈,空减栈
FA:Full Ascending 满增
FD:Full Descending 满减
EA:Empty Ascending 空增
ED:Empty Descending 空减
ARM默认采用的是满减栈
stmfd/ldmfd<code> sp!, {寄存器列表}
stmfd sp!, {r1-r5}(写) (压栈)
更新栈指针指向的地址空间
ldmfd sp!, {r6-r10}(读) (出栈)
特殊:(CPU寄存器不连续)
stmfd sp!, {r1-r5,lr}(写) (压栈)
ldmfd sp!, {r6-r10,pc}(读) (出栈) //r1-r5出栈给r6-r10, 将lr的值出栈给pc
程序中运用
ldr r1,=0x11111111
ldr r2,=0x22222222
ldr r3,=0x33333333
ldr r4,=0x44444444
ldr r5,=0x55555555
ldr sp,=0x40001020
stmfd sp!,{r1-r5}
bl func
ldmfd sp!,{r1-r5}
ldr r6,=0x66666666
loop:
b loop
func:
ldr r1,=0x1
ldr r2,=0x2
ldr r3,=0x3
ldr r4,=0x4
mov pc,lr
栈的应用-》叶子函数的调用过程
叶子函数是指一个函数内部没有调用其他函数的函数,也就是说,它是程序调用树的末端节点,不依赖于其他函数。
MOV SP,#0X40000020
MAIN:
MOV R1,#3
MOV R2,#2
BL F
ADD R3,R1,R2
T:
B T
F:
STMFD SP!,{R1,R2}
MOV R1,#5
MOV R2,#4
ADD R3,R1,R2
LDMFD SP!,{R1,R2}
MOV PC,LR
栈的应用-》非叶子函数的调用过程
非叶子函数是指一个函数内部调用了其他函数的函数,也就是说,它不是程序调用树的末端节点,可以被其他函数调用。
MOV SP,#0X40000020
MAIN:
MOV R1,#3
MOV R2,#2
BL F
ADD R3,R1,R2
T:
B T
F:
STMFD SP!,{R1,R2,LR}
MOV R1,#5
MOV R2,#4
BL D
ADD R3,R1,R2
LDMFD SP!,{R1,R2,LR}
MOV PC,LR
D:
STMFD SP!,{R1,R2,LR}
MOV R1,#6
MOV R2,#7
ADD R3,R1,R2
LDMFD SP!,{R1,R2,LR}
MOV PC,LR
5、状态寄存器操作指令
注:刚上电是在SVC模式下
对CPSR进行读写操作
//其他都不能动CPSR (SWI 指令是linux内核有,所以arm为了匹配才有的指令)
(CPSR保存cpu的状态、模式、中断中断开关、运算状态,非常重要,不能任意更改,只有一类指令能操作这个寄存器)
读cpsr指令 mrs
写cpsr指令 msr
@状态寄存器操作指令 mrs / msr
ldr r1,=0x12345678
mrs r0,cpsr @读cpsr的值到r0
msr cpsr_c,#0x1f @修改cpsr的值,cpsr_c 指的是后八位控制位
msr cpsr_c,#0x13 @切换到svc
msr cpsr_c,#0x10 @切换到user,非特权模式
ldr r2,=0x87654321
msr cpsr_c,#0x13 @切换到svc
注:修改CPSR的控制域(bit[7:0]),修改CPSR时必须指定修改哪个区域;USER模式下不能修改CPSR的值,防止应用程序修改CPU状态,保护操作系统;CPSR_C修改的是CPSR的低八位ctrl(控制)域,一般都只修改C域。
6、异常中断指令
触发软中断,常用于内核的系统调用 //SWI:软中断
@异常中断指令 软中断指令swi
ldr r1,=0x12345678
ldr r2,=0x22345678
ldr r4,=0x42345678
msr cpsr_c,#0x10 @切换到user模式
ldr r3,=0x32345678
swi #1 @软中断
@执行软中断指令:spsr、lr、cpsr、pc 均发生了改变
ldr r6,=0x52345678
为什么会出现以上情况?做完的请先自行阅读下方文档
异常源及处理过程
7、协处理器指令
操作协处理器的指令(一般用不到-----协助cpu处理数据)
1.数据运算
2.内存访问
3.与主处理器通信
MRC 将协处理器中寄存器的内容读取到ARM处理器的寄存器中
MCR 将ARM理器中寄存器的内容读取到协处理器的寄存器中
协处理器指令
- 协处理器数据运算指令
CDP
- 协处理器储存器访问指令
STC 将协处理器中的数据储存到存储器
LDC 将存储器中的数据读取到协处理器中
- 协处理器寄存器传送指令
MRC 将协处理器中寄存器的数据传送到ARM处理器中的寄存器
MCR 将ARM处理器寄存器中的数据读取到协处理器寄存器中
伪指令
本质:本身不是指令,但是cpu替换成等效的操作。
举例1:
延时一个指令周期(耗时一条指令的时间) (cpu没有这个指令)
NOP ;执行NOP和MOV R0,R0一个效果,执行NOP,cpu替换成MOV R0,R0
MOV R0,R0
LDR的两种形式
;->指令
LDR R1,[R2]
;->伪指令
LDR R1,=0x12345678 ;R1 = 0x12345678
;可以将任何一个32bit的数据放入寄存器
伪操作
指令是arm公司规定的,而伪操作是编译器规定的,不同的编译器伪操作指令不同。
(我们学的linux,用linux的编译器)