ARM-进阶汇编指令
目录
一、比较指令
1.1指令码
1.2指令格式
1.3测试代码
二、跳转指令
2.1指令码
2.2指令格式
2.3测试代码
2.4练习题
三、单寄存器内存读写指令
3.1前言
3.2指令码
3.3指令格式
3.4测试代码
3.5地址偏移
3.6地址偏移测试代码
3.7练习题
四、栈指针寄存器内存读写指令
4.1栈的分类
4.2指令码
4.3指令格式
4.4寄存器列表的编写规则
4.5测试代码1
4.6测试代码2
4.7测试代码3
五、CPSR 特殊功能寄存器读写指令
5.1指令码
5.2指令格式
5.3测试代码
六、swi软中断指令
6.1介绍
6.2指令格式
6.3软中断指令的作用
6.4测试代码
一、比较指令
1.1指令码
cmp --- 比较指令指令码
1.2指令格式
cmp Rn,oprand_shifter2
注意:1.比较指令就相当于c语言语句中的if语句
2.比较指令不需要实用目标寄存器,比较完的值不需要存储
3.比较指令的本质是减法,也就是Rn - oprand_shifer2
4、比较指令的执行结果会直接影响CPSR寄存器的NZCV位,使用比较指令时不需要+s
5、比较指令通常会搭配条件码({cond})进行使用(并不是在cmp这条汇编指令后+cond)
1.3测试代码
.text
.globl _start_start:mov r1,#0x1mov r2, #0x2cmp r1,r2addcs r3,r1,r2 @解释:当r1>=r2时,r3=r1+r2subcc r3,r1,r2 @解释:当r1<r2时,r3=r1-r2stop:b stop.end
二、跳转指令
2.1指令码
b ------ 有去无回的跳转
bl ------ 有去有回的跳转
l:LR寄存器
2.2指令格式
b/bl 标签(函数名)
2.3测试代码
.text
.globl _start_start:mov r1, #0x1mov r2, #0x2bl funcadd r3,r1,r2func:mov r1, #0x5mov r2, #0x5add r3,r1,r2mov pc,lrstop:b stop.end
2.4练习题
.text
.globl _start_start:mov r0, #0x1mov r1, #0x2func:cmp r0, r1 beq stopblne func1func1:cmp r0,r1subhi r0,r0,r1subls r1,r1,r0b funcstop:b stop.end
三、单寄存器内存读写指令
3.1前言
int a=10;
int *p=&a;
*p=20;
3.2指令码
ldr ----- 从内存空间中读取4个字节的数据
ldrh ----- 从内存空间中读取2个字节的数据
ldrb ----- 从内存空间中读取1个字节的数据
str ------ 向内存空间中写入4个字节的数据strh ----- 向内存空间中写入2个字节的数据
shrb ----- 向内存空间中写入1个字节的数据
ld: load(加载,从内存地址空间中去读取数据)st: store(存储,向内存地址空间中写入数据)
r: register(4个字节)
h:half word(2个字节)
b:byte(1个字节)注:1个字=4个字节
3.3指令格式
ldr/ldrh/ldrb R0,[Rn]
解释:
将[Rn]寄存器中的值看做是一片内存空间地址
从Rd寄存器中的4个/2个/1个字节的数据写入到【Rn】这片内存空间地址中
注意:
1、当汇编指令中寄存器编号用[]修饰时,当前寄存器中的值会被看做是一片内存空间的地址
3.4测试代码
.text
.globl _start_start:ldr r0,=0x40000820ldr r1,=0x12345678str r1,[r0] @将r0寄存其中的值看做是一片内存空间地址@将r1寄存器中4个字节的数据写到[r0]这片空间地址中ldr r2,[r0] @将r0寄存器中的值看做是一片内存空间地址@将r0这片内存空间地址中的值读取到r2寄存器中stop:b stop.end
3.5地址偏移
int arr[] = {12,1,2,3,4}
int *p=arr;
printf("%d\n",*(p+1));
三种内存空间地址偏移的写法
1、
ldr/ldrh/ldrb Rd,[Rn],#offset解释:
将Rn寄存器中的值看做是一片内存空间地址
从[Rn]这片内存空间地址中读取4个/2个/1个字节的数据到Rd寄存器中
注意:
当读取完数据后,Rn寄存器中的地址发生变化Rn=Rn+#offset
2、
ldr/ldrh/ldrb Rd,[Rn, #offset]
解释:
将Rn+#offset的值看做一片内存空间地址
从Rn+#offset这片内存空间地址中读取4个/2个/1个字节的数据到Rd寄存器中
注意:
当读取完整数据后,Rn寄存器中的地址不会发生变化
3、ldr/ldrh/ldrb Rd,[Rn,#offset]!
解释:
将Rn+#offset的值看做是一片内存空间地址
从Rn+#offset这片内存空间地址中读取4个/2个/1个字节的数据到Rd寄存器中
注意:
当使用!时,当读取完数据后,Rn寄存器中的地址发生改变r0=r0+#offset注意:
1、#offset就是一个值,就是一个地址偏移量(#1,#4,#8)
2、str/strh/strb地址偏移的效果和上述效果类似(从读取变为写入)
3、使用str/ldr地址偏移时,#offset地址偏移量必须要是4的倍数
4、使用strh/ldrh地址偏移时,#offset地址偏移量必须要是2的倍数
5、使用strb/ldrb地址偏移时,#offset地址偏移量必须要是1的倍数
3.6地址偏移测试代码
/**********************9、单寄存器内存读写指令 - 地址偏移**************************/ldr r0, =0x40000820ldr r1, =0x12345678str r1, [r0] @ 执行完上述操作,0x40000820地址中存在0x12345678这个值ldr r2, [r0], #4/* 解释:将r0寄存器中的值(0x40000820)看作是一片内存空间地址从0x40000820这片内存空间地址读取4个字节的数据(0x12345678)到r2寄存器中r2 = 0x12345678读取完数据后,r0寄存器中的地址发生变化 r0 = r0 + 4 = 0x40000820 + 4 = 0x40000824*/ldr r0, =0x4000081Cldr r3, [r0, #4]/* 解释:将r0+#4看作是一片内存空间地址(0x4000081C+4=0x40000820)从0x40000820这片内存空间地址中读取4个字节的数据到r3寄存器中r3 = 0x12345678r0 = 0x4000081C*/ldr r4, [r0, #4]!/* 解释:将r0+#4看作是一片内存空间地址(0x4000081C+4=0x40000820)从0x40000820这片内存空间地址中读取4个字节的数据到r3寄存器中r4 = 0x12345678r0 = r0 + #4 = 0x4000081C + 4 = 0x40000820*/
3.7练习题
/*0x40000820这片内存空间中存储了0x12345678这个数据通过ldrb和地址偏移的三种方法,将0x40000820这片内存空间地址中的值读取到r1(0x12), r2(0x34), r3(0x56), r4(0x78)这四个寄存器中将r1, r2, r3, r4 这四个寄存器中的值拼接到r5寄存器中(r5 = 0x12345678)*/ldr r0, =0x40000820ldr r6, =0x12345678str r6, [r0]@ 1、从内存空间读取数据@ 1.1 ldrb Rd, [Rn], #offset/*ldrb r4, [r0], #1 @ r0 = 0x40000820 ldrb r3, [r0], #1 @ r0 = 0x40000821ldrb r2, [r0], #1 @ r0 = 0x40000822ldrb r1, [r0], #1 @ r0 = 0x40000823*/@ 1.2 ldrb Rd, [Rn, #offset]/*ldrb r4, [r0, #0] @ 读取的内存空间地址:0x40000820ldrb r3, [r0, #1] @ 读取的内存空间地址:0x40000821ldrb r2, [r0, #2] @ 读取的内存空间地址:0x40000822ldrb r1, [r0, #3] @ 读取的内存空间地址:0x40000823*/@ 1.1 ldrb Rd, [Rn, #offset]!ldrb r4, [r0, #0]! @ 读取的内存空间地址:0x40000820, r0 = 0x40000820ldrb r3, [r0, #1]! @ 读取的内存空间地址:0x40000821, r0 = 0x40000821ldrb r2, [r0, #1]! @ 读取的内存空间地址:0x40000822, r0 = 0x40000822ldrb r1, [r0, #1]! @ 读取的内存空间地址:0x40000823, r0 = 0x40000823@ 2、将数据拼接到r5寄存器中@ 2.1 左移+按位或操作/*lsl r4, r4, #0lsl r3, r3, #8lsl r2, r2, #16lsl r1, r1, #24orr r5, r5, r4orr r5, r5, r3orr r5, r5, r2orr r5, r5, r1*/orr r5, r5, r4, lsl #0orr r5, r5, r3, lsl #8orr r5, r5, r2, lsl #16orr r5, r5, r1, lsl #24@ 2.2 左移+加法运算lsl r4, r4, #0lsl r3, r3, #8lsl r2, r2, #16lsl r1, r1, #24add r5, r5, r4add r5, r5, r3add r5, r5, r2add r5, r5, r1
四、栈指针寄存器内存读写指令
4.1栈的分类
增栈:
当进行压栈操作时,栈指针的指向向高地址方向移动
当进行出栈操作时,栈指针的指向向低地址方向移动
减栈:
当进行压栈操作时,栈指针的指向向低地址方向移动
当进行出栈操作时,栈指针的指向向高地值方向移动
满栈:
当前栈指针指向的地址空间内存在有效数据
当使用满栈进行压栈操作时,需要先移动栈指针的指向,在压入新的数据(防止有效数据被新的数据覆盖)
空栈:
当前栈指针指向的地址空间内不存在有效数据
当使用空栈进行压栈操作时,可以直接压入新的有效数据
栈的分类:
满减栈(Full Descending Stack) ------ 对于ARM架构而言,使用的是满减栈
满增栈(Full Ascending Stack)
空减栈(Emoty Descending Stack)
空增栈(Emoty Descending Stack)
4.2指令码
满减栈(Full Descending Stack) ------ ldmfd/stmfd
满增栈(Full Ascending Stack)------ldmfa/stmfa
空减栈(Emoty Descending Stack)------ldmed/stmed
空增栈(Emoty Descending Stack)------ldmea/stmea
m:mutiple(多个、多种)
4.3指令格式
满减栈(Full Descending Stack):当进行压栈操作时,栈指针的指向先向低地址方向移动,在压入新的数据
ldmfd sp!,{寄存器列表}!的作用:用于更新栈指针的指向
解释:
从栈指针指向的一片内存空间地址中读取数据到寄存器列表中
stmfd sp! , {寄存器列表}
!的作用:用于更新栈指针的指向
解释:
将寄存器列表中的值写入到栈指针的一片内存空间地址中
当进行压栈操作时,sp的指向先向低地址方向移动,在压入新的数据
sp=0x40000820
16=0x10
sp=0x40000820-0x10=0x40000810
4.4寄存器列表的编写规则
1、寄存器列表中用于存放多个寄存器的编号
2、当寄存器列表中寄存器编号连续时,此时使用-进行分割,如{r0-r5}
3、当寄存器列表中寄存器编号不连续时,此时使用,进行分隔,如{r1,r3,r5,r7}
4、寄存器列表中可以存在部分连续,部分不连续的寄存器编号,如{r1-r4,r6,r8-r9}
5、寄存器列表中的寄存器编号一般采用从小到大的方式编写
也可以从大到小写,但是如果从大到小写,只能使用,分隔,如{r4,r3,r2,r1}
4.5测试代码1
.text
.globl _start_start:ldr sp, =0x40000820ldr r1, =0x11111111ldr r2, =0x22222222ldr r3, =0x33333333ldr r4, =0x44444444stmfd sp!,{r1-r4}@解释:@将r1-r4寄存器中的值写入到sp栈指针指向的空间地址中@由于使用的是满减栈,栈指针会先移动指向(向低地址方向移动),在压入数据@sp=0x40000820-0x10(16个字节)=0x40000810ldmfd sp!, {r5-r8}@解释:@将sp栈指针指向的内存空间地址中的数据读取到r5-r8寄存器中@此时在做出栈操作,sp的指向会向高地址方向移动@sp=0x40000810+0x10(16个字节)=0x40000820stop:b stop.end
4.6测试代码2
int main(void)
{
int r1=0x1,r2=0x2;func();
add r3,r1,r2
}
void func(void)
{
int r1=0x5,r2=0x5;sub r3,r1,r2
}
.text
.globl _start_start:@使用栈指针寄存器本质是为了实现c语言中的局部变量的功能@为实现局部变量的功能,需要将r1和r2寄存器中的值先压入到栈空间地址中ldr sp, =0x40000820mov r1,#0x1mov r2,#0x2@此处可以进行压栈操作@压栈操作取决于是否二次使用这个寄存器@只有在还没有二次使用这个寄存器的位置进行压栈即可bl funcadd r3,r1,r2b stopfunc:@此处压栈stmfd sp!,{r1-r2}mov r1, #0x5mov r2, #0x5sub r3,r1,r2@出栈和上面一样的ldmfd sp!,{r1-r2}mov pc,lrstop:b stop.end
4.7测试代码3
int main(void)
{
int r1=0x1,r2=0x2;func1();
add r3,r1,r2
}
void func1(void)
{
int r1=0x5,r2=0x5;func2();
sub r3,r1,r2
}
void func2(void)
{
int r1=0x9,r2=0x1;func3();
mul r3,r1,r2;
}
void func3(void)
{
int r1=0x1,r2=0x6;add r3,r1,r2;5
}
.text
.globl _start_start:@使用栈指针寄存器本质是为了实现c语言中的局部变量的功能@为实现局部变量的功能,需要将r1和r2寄存器中的值先压入到栈空间地址中ldr sp, =0x40000820mov r1,#0x1mov r2,#0x2@此处可以进行压栈操作@压栈操作取决于是否二次使用这个寄存器@只有在还没有二次使用这个寄存器的位置进行压栈即可bl func1add r3,r1,r2func1:@此处压栈stmfd sp!,{r1-r2,lr}mov r1, #0x5mov r2, #0x5bl func2sub r3,r1,r2@出栈和上面一样的ldmfd sp!,{r1-r2,pc}@mov pc,lr@洗这个存在问题因为在多次调用时lr的值会被覆盖,解决办法用寄存器存储lrd的值func2:stmfd sp!,{r1-r2,lr}mov r1, #0x9mov r2, #0x1bl func3mul r3,r1,r2ldmfd sp!,{r1-r2,pc}func3:stmfd sp!,{r1-r2,lr}mov r1, #0x1mov r2, #0x6add r3,r1,r2ldmfd sp!, {r1-r2,pc}stop:b stop.end
五、CPSR 特殊功能寄存器读写指令
5.1指令码
msr ----- 向cpsr寄存器中写入数据
mrs ----- 从cpsr寄存器中读取数据
5.2指令格式
msr CPSR, oprand_shifter2
解释:将oprand_shifter2第二操作数写入到CPSR寄存器中
mrs Rd,CPSR解释:从CPSR寄存器中读取数据到Rd寄存器中
!!!!!!!只有mrs和msr这两条汇编指令可以操作CPSR寄存器,其他指令的汇编指令无法直接操作CSR寄存器
5.3测试代码
.text
.globl _start_start:@已知CPSR寄存器中的值为0xD3(正常情况下我们是不知道的)@目的:我们需要将当前工作模式从SVC模式切换到user模式@我们可以改变cpsr寄存器中的M[4:0]位,其他的位不变@此时我们需要用到位运算相关的汇编指令@问题:普通的汇编指令无法直接操作CSPR寄存器,需要通过msr和mrs这条汇编指令来操作CPSR寄存器@1、可以先使用mrs将cpsr寄存器中的值读取的普通寄存器中@2、在通过and和orr修改普通寄存器中的值@3、将修改完的值通过msr写入到cpsr寄存器中mrs r0, cpsrand r0,r0, #(~(0x1f<<0))orr r0,r0, #(0x1<<4)msr cpsr,r0stop:b stop.end
六、swi软中断指令
6.1介绍
软中断:软件代码产生的中断
swi:software interrupt
6.2指令格式
指令格式:swi 软中短号
软中短号就是一个数字(0-255)
6.3软中断指令的作用
作用:不管当前外设处于那种工作模式下,当执行软中断指令时,默认切换到SVC模式下
6.4测试代码
软中断相关的异常在单片机课程中的中断实验详细讲解
mrs r0,cpsr and r0,r0,#(~(0x1f<<0)) orr r0,r0,#(0x1<<4) msr cpsr,r0 @软中断指令 swi 10 @当执行完这条汇编指令,工作模式默认切换到SVC模式下 mov r1,#0x1