开发环境阐明

本文采用的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。

【汇编】深入浅出地讲解使用六条汇编指令解决大部分汇编代码
在压入一个数值0x03后,栈顶指针会向上移动,此刻其指向的地址为0x00000018。倘若持续压入数值,栈顶指针仍是会持续向上移动,地址会持续依次减少。
【汇编】深入浅出地讲解使用六条汇编指令解决大部分汇编代码
每次栈履行一次出栈操作,栈顶指针就会向下移动,那么其指向的地址就会不断增加。
【汇编】深入浅出地讲解使用六条汇编指令解决大部分汇编代码
因此,就可以理解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指令履行成果不会影响标志位的改变。