开发环境说明
本文采用的IDE为keil5,核心板为STM32F103ZE。
在keil中添加以下两条语句即可生成汇编代码:在上图界面中添加下述两句代码即可生成汇编代码:
fromelf --bin --output=test.bin Objects\test.axf
fromelf --text -a -c --output=test.dis Objects\test.axf
指令说明:fromelf是反向汇编指令,output后接生成目标文件名字,最后的Objects\test.axf是源程序,也就是项目工程编译后产生的文件;一般情况下修改的是目标文件的名字。
汇编指令
读取内存指令LDR
在汇编中,LDR指令其实是一对“孪生兄弟”,即有个称为LDR指令,另一个称为LDR伪指令,尽管他们是孪生兄弟,但其作用却大不相同。
小编将要讲述的是LDR指令,操作原型可为 LDR p1,[p2,#4]
,其意思是将地址p2+4中的数值加载到p1上,也就是读取地址再赋值。
写内存STR指令
STR指令,其操作原型为STR p1,[p2,#4]
,其意思是将数值p1写入地址p2+4中,也就是直接写入数据。
加法ADD指令
ADD指令,其操作原型为 ADD r1,r2,r3
或者是 ADD r1,r2,#2
,其意思很简单,就是r1=r2+r3
或者是<r1=r2+2>,简单的说就是第一个数的值等于后面两个数之和。
减法SUB指令
减法指令与加法指令类似。
SUB指令,其操作原型为 ADD r1,r2,r3
或者是 ADD r1,r2,#2
,其意思很简单,就是r1=r2-r3
或者是<r1=r2-2>,简单的说就是第一个数的值等于后面两个数之差。
比较CMP指令
CMP指令,其操作原型为 cmp p1,p2
,简单的说就是比较两个值的大小,其结果保存在程序状态计数器PSR中。
跳转B/BL指令
B/BL指令的作用是实现程序跳转,也就是调用子程序。
它们之间的区别是:
- B指令,只是简单的程序跳转,并且执行目的子程序;
- BL指令,是带链接跳转,也就是需要返回地址。程序在发生跳转前,需要将当前PC值保存在R14中,也就是保存返回地址。
用法示例:
B label
,意思是跳转label处理程序;
BL label
,意思是跳转到label函数,同时将PC值保存到R14中。
示例讲解
在汇编语言中栈的结构讲解
栈大小初始化完成后,栈结构如下,此时栈顶指针应该指向底部,其地址为0x0000001b。
r1=sp+4
的原因了。 **示例代码1** 示例1,是一个简单的两变量相加然后保存在第三个变量的程序。 *C语言程序:*
#include "stm32f10x.h" // Device header
int main(void)
{
volatile int a=123,b=1,c=28;
c = a+b;
return 0;
}
汇编代码:
上述程序会生成下述的汇编代码:
i.main
main
0x080003b4: b50e .. PUSH {r1-r3,lr} ;将r1,r2,r3寄存器入栈,并且保存r1,r2,r3寄存器之前的值与返回地址
0x080003b6: 207b { MOVS r0,#0x7b ;将0x7b(十进制的123)赋值给r0寄存器
0x080003b8: 9002 .. STR r0,[sp,#8] ;保存参数的值,将其放在sp+8的地方,此时r0保存的是参数值
0x080003ba: 2001 . MOVS r0,#1 ;将0x01(十进制的1)赋值给r0寄存器
0x080003bc: 9001 .. STR r0,[sp,#4] ;保存参数的值,将其放在sp+4的地方,此时r0保存的是参数值
0x080003be: 201c . MOVS r0,#0x1c ;将0x1c(十进制的28)赋值给r0寄存器
0x080003c0: 9000 .. STR r0,[sp,#0] ;保存参数的值,将其放在sp的地方,此时r0保存的是参数值
0x080003c2: e9dd1001 .... LDRD r1,r0,[sp,#4] ;这里的实质是r1=sp+4,r0=sp+8,也就是获取前两步赋值操作的值
0x080003c6: 4408 .D ADD r0,r0,r1 ;加法操作,也就是r0=r0+r1
0x080003c8: 9000 .. STR r0,[sp,#0] ;保存参数的值,将其放在sp+0的地方,此时r0保存的是参数值
0x080003ca: 2000 . MOVS r0,#0 ;将数值0赋值给r0寄存器
0x080003cc: bd0e .. POP {r1-r3,pc} ;弹出r1,r2,r3寄存器,并且将地址从堆栈中弹出到pc,开始执行lr保存的程序
0x080003ce: 0000 .. MOVS r0,r0 ;将r0的值赋值给r0,这里应该是面向上述给r0赋值为0的操作
示例代码2
在这里,小编写了一个简单的C语言加法程序来计算两变量之和,最终保存在第三个变量中。
C语言程序:
#include "stm32f10x.h" // Device header
void addNum(int*a1,int*b1,int*c1)
{
*c1 = *a1+*b1;
}
int main(void)
{
volatile int a=123,b=1,c=28;
addNum(&a,&b,&c);
return 0;
}
汇编代码 :
i.addNum
addNum
0x080003b4: b510 .. PUSH {r4,lr} ;将r4寄存器入栈,并且保存r4寄存器之前的值与返回地址
0x080003b6: 6803 .h LDR r3,[r0,#0] ;读取r0寄存器中的值并且将其赋值给r3
0x080003b8: 680c .h LDR r4,[r1,#0] ;读取r1寄存器中的值并且将其赋值给r4
0x080003ba: 4423 #D ADD r3,r3,r4 ;将r3,r4寄存器中的值相加保存到r3中
0x080003bc: 6013 .` STR r3,[r2,#0] ;将r3寄存器中保存的数据写入到r2中
0x080003be: bd10 .. POP {r4,pc} ;弹出r4寄存器,并且将地址从堆栈中弹出到pc,开始执行lr保存的程序
i.main
main
0x080003c0: b50e .. PUSH {r1-r3,lr} ;将r1,r2,r3寄存器入栈,并且保存r1,r2,r3寄存器之前的值与返回地址lr
0x080003c2: 207b { MOVS r0,#0x7b ;将数值0x7b(十进制123)赋值给r0寄存器
0x080003c4: 9002 .. STR r0,[sp,#8] ;将r0寄存器中保存的数据写入到sp+4地址中
0x080003c6: 2000 . MOVS r0,#0 ;将数值0(十进制0)赋值给0
0x080003c8: 9001 .. STR r0,[sp,#4] ;将r0寄存器中保存的数据写入到sp+4地址中
0x080003ca: 9000 .. STR r0,[sp,#0] ;将r0寄存器中保存的数据写入到sp+4地址中
0x080003cc: 466a jF MOV r2,sp ;将栈指针sp保存的值赋值给r2
0x080003ce: a901 .. ADD r1,sp,#4 ;这是一个加法等式,也就是r1=sp+4,其实质还是将sp+4地址中的值赋值给r1寄存器
0x080003d0: a802 .. ADD r0,sp,#8 ;这是一个加法等式,也就是r0=sp+8,其实质还是将sp+8地址中的值赋值给r0寄存器
0x080003d2: f7ffffef .... BL addNum ; 0x80003b4 这是一个跳转指令,跳转到子程序addNum并且保存当前地址
0x080003d6: 2000 . MOVS r0,#0 ;将值0赋值给r0
0x080003d8: bd0e .. POP {r1-r3,pc} ;弹出r1,r2,r3寄存器,并且将地址从堆栈中弹出到pc,开始执行lr保存的程序
0x080003da: 0000 .. MOVS r0,r0 ;将r0的值赋值给r0,这里应该是面向上述给r0赋值为0的操作
小结
- 汇编程序一旦以push开头,那么必定会以pop结尾,并且push指令中一定会含有一个lr、pop指令中一定会存在一个pc。
- MOV与MOVS指令的异同:
-
- 同:两者的功能都是传送数据,也就是赋值;
-
- 异:带MOVS指令执行结果会影响标志位的变化,而MOV指令执行结果不会影响标志位的变化。