zoukankan      html  css  js  c++  java
  • 前期准备——2.基本汇编语法

    在做裸机开发前,我们要掌握一些基础的ARM汇编语法,因为即使后面我们用C去写驱动,也要用汇编去执行配置指针、中断、清除session等操作。我们使用的芯片是I.MX6UL,这是款Cortex-A7的内核芯片,所以使用的就是Cortex-A的汇编指令,这里有两份资料可以参考点击下载(提取码:l1rg)。还好我们主要目的就是进行系统的初始化,用到的都是比较简单的指令,也不会涉及到复杂的代码结构。

    GNU汇编语法

    语法结构

    GNU汇编语法是适用于所有的架构,并不是ARM独享的,GNU汇编由一系列的语句组成,每条语句结构如下:

    label: instruction @ comment

    label:标号,用来表示地址位置,有些指令前面可能会有标号,这样可以通过标号来找到指令的地址注意标号后面有个冒号,在汇报中,任何以冒号结尾的表示符都会被识别为标号

    instruction:指令,包括汇编指令或伪指令

    @:注释符,也可以用/**/来包裹注释内容

    comment:注释内容

    比如我们在第一个用汇编点亮LED的试验中的一段汇编代码

     第二行的_start就是就是标号,第6行的

    ldr r0,=0x020c4068

    就是指令。注意指令中的指令、伪指令、寄存器、寄存器名可以全部使用大写,也可以全部使用小写,但是切记不能大小写混用

    后面的@CCGR0就是表明注释内容。

    常用的伪操作

    在定义标号时,要知道有些标号是常用的伪操作

    • .byte  定义单字节数据
    • .short   定义双字节数据
    • .long  定义4字节数据
    • .equ   赋值语句  比如.equ num,0x12,表示num=0x12
    • .align  字节对齐,比如.align 4 表示4字节对齐,这个在后面定义session会经常用到
    • .end  表示源文件结束
    • .global 定义全局符号,比如上面的.global _start。

    并且在在使用.session伪操作定义段的时候,汇编已经预定义了一些段名:

    • .text——代码段
    • .data——初始化的数据段
    • .bss——未初始化的数据段
    • .rodata——只读数据段

    函数

    GNU也是支持函数的,函数的格式如下:

    函数名:
        函数体
        返回语句

    可以发现,函数声明的方法和前面的基础语法一样,就是把label换成了函数名。返回值在函数里不是必要的。比如下面这段代码就是我们在讲中断时定义的函数

    /*复位中断服务函数 */
    Reset_Handler:
        ldr r0,=Reset_Handler
        bx r0
    
    /*未定义指令中断服务函数 */
    Undefined_Handler:
        ldr r0,=Undefined_Handler
        bx r0
    
    /*SVC中断服务函数 */
    SVC_Handler:
        ldr r0,=SVC_Handler
        bx r0

    常用汇编语句

    下面看看我们常用的汇编语句

    处理器内部数据传输指令

    处理器做的最多的事情就是在处理器内部来回传递数据,这些数据操作基本为

    • 通用寄存器之间数据相互传递
    • 通用寄存器与特殊寄存器(如CPSR、SPSR等)之间数据相互传递
    • 寄存器存入立即数

    数据传输常用的指令有三个:MOV、MRS和MSR,这三个指令的用法如下:

    指令 目的 数据源 说明
    MOV R0 R1 将寄存器R1内数据复制到寄存器R0中
    MRS R0 CPSR 将特殊寄存器CPSR内的数据复制到R0中
    MSR CPSR R0 将R0内数据复制到特殊寄存器CPSR内

    下面分别介绍下3个指令的具体用法

    MOV指令

    MOV指令用来将一个寄存器里的数据拷贝到另一个寄存器内,也可以将一个立即数拷贝到寄存器内,用法如下

    MOV R0,R1       @将R1内的数据传递给R0
    MOV R0,#0xFF    @将立即数0xFFC传递给R0

    MRS指令

    MRS是将数据从特殊寄存器传递个通用寄存器,也就是用于读取特殊寄存器的值,用法如下:

    MRS R0,CPSR     @将CPSR的数据传递给R0

    MSR指令

    MSR指令是将数据从通用寄存器传递给特殊寄存器,也就是写通用寄存器

    MSR CPSR,R0    @将R0的数据传递给CPSR

    MSR和MRS两个指令非常容易混淆,有个简单的方法记忆:因为指令后面两个寄存器方向是固定的,前面的是目标,后面的是源,R可以记成通用寄存器,S刚好和特殊的头文字相符,就记成特殊寄存器,MRS就是从特殊到通用,MSR就是从通用到特殊。

    存储器访问指令

    ARM处理器是不能直接访问存储器的,这里的存储器不是SD卡或者NANDFlash,而是类似RAM中的数据,它必须借助内核寄存器组,也就是R0~R15来实现功能,我们需要用汇编来配置I.MX6UL寄存器的时候需要借助存储器访问指令,一般就是将要配置的参数写入到通用寄存器(R0~R12)中,然后借助存储器访问指令将R存储器中的数据写入到I.MX6UL寄存器中,读取寄存器也是一样的,只是过程相反。常用的访问存储器的指令有两种:LDR和STR:用法如下表

    指令 说明
    LDR Rd,[Rn,#offset]

    从存储器Rn+offset的位置读取数据放到Rd中

    STR Rd,[Rn,#offset] 将Rd里的数据写入到Rn+offset的位置

    下面来看看这两条指令是怎么用的:

    LDR指令

    LDR用于从RAM里读取寄存器的值,比如我们要读取地址为0x0209C004这个寄存器(GPIO1_GDIR,控制GPIO1输入输出功能的寄存器,点灯时候会用到)的值,就要这么做

    LDR R1,=0x0209C004
    LDR R0,[R1]

    就是现在R1中保存要读取寄存器的地址,再把R1地址中的数据传给R0。注意这里传递立即数的时候和MOV指令有区别,一个用的是#,一个用的是=

    STR指令

    STR指令和LDR相反,用来设置寄存器的值,比如我们要讲GPIO1_GDIR的值设置为0x00000001,代码如下:

    LDR R1,=0x0209C004
    LDR R0,=0x00000001
    STR R0,[R1]

    LDR和STR是按照字节进行读取和写入到,也就是操作得都是32位的数据,如果要按照字节或板子姐进行操作得话可以来指令后加上B(Byte)或者H(Half)。同样我们找个方便记忆的方法:LDR可以记忆成LosdR,也就是读取寄存器,STR是setR,设置,也就是写入寄存器。

    压栈和出栈

    我们通常在中断等操作中需要在A函数中调用B函数,当B函数执行完成后继续执行A函数,要想在跳回A函数时代码能够正常运行,就需要在跳转B函数前讲当前处理器状态保存(也就是保存R0~R15的值),在B函数执行完毕后将保存的值恢复值R0~R15即可。所以保存R0~R15的过程就叫现场保护,恢复的过程就叫恢复现场。在现场保护的时候要进行压栈操作,恢复现场就是进行出栈操作。我们有两个指令来进行该擦操作:PUSH和POP

    指令 说明
    PUSH <reg list>

    将寄存器列表存入栈中

    POP <reg list>

    从栈中恢复到寄存器列表

    假如我们现在要把R0~R3和R12这5个寄存器压栈,当前的SP指针指向0x80000000,处理器的堆栈为向下增长,使用的代码就是这样的

    PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈

    一定要注意堆栈指针的增长方向,入栈以后的堆栈如下图所示

    现在的指针就指向0x7FFFFFEC,假如此刻我们需要对LR寄存器入栈,那么执行完下面代码

    PUSH {LR}    @将LR压栈

    堆栈模型如下:

     在这种情况下我们如果要出栈,就要执行下面的代码

    POP {LR}      @先恢复 LR
    POP {R0~R3,R12}  @在恢复 R0~R3,R12

    出入栈的另一种方法

    出栈就是从栈盯,也就是SP当前执行的开始位置,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中,PUSH和POP的另一种方法就是 STMFD SP!和LDMFD SP!所以上面的代码也可以写成这样:

    STMFD SP!,{R0~R3, R12}     @R0~R3,R12 入栈
    STMFD SP!,{LR}                    @LR 入栈
    LDMFD SP!, {LR}                   @先恢复 LR
    LDMFD SP!, {R0~R3, R12}    @再恢复 R0~R3, R12

    STMFD可以被分解为两部分:STM和FD,同样,LDMFD也可以被分解成LDM和FD这个STM和LDM就跟前面我们讲过LDR和STR,但是这两个指令只能读取存储器中一个数据,而这两个STM和LDM可以多存储和多加载,可以操作存储器内多个连续数据。FD是Full Descending的缩写,即满递减的意思,根据ATPCS规则,ARM使用的FD类型的堆栈,SP指向最后一个入栈的数值,堆栈是由高地址向下增长的,所以用的是STMFD的指令。STM和LDM的指令寄存器列表中标号小的对应低地址,编号高度对应小地址。

    跳转指令

    在代码中我们经常需要程序跳转至别的代码段,这就是跳转操作。有两种方法我们比较常用:

    1. 使用跳转指令:B、BL、BX
    2. 直接向PC寄存器写入数据

    第一种方法我们是最常用的,指令用法如下:

    指令 说明
    B <Label>

    跳转到label处,如果跳转范围超过了±2KB,可以指定B.W来使用32位版本的跳转指令。

    BX <Rm>

    间接跳转,Rm内存放数据为跳转到目的地址,并且切换指令集

    BL <Lable>

    跳转到Label处,并将返回地址保存在LR(Link Register)里,可以回到跳转前的位置

    BLX <Rm>

    结合了BX和BL的特点,间接跳转至Rm保存的地址,并保存返回地址再LR内,切换指令集。

    B指令

    最简单的跳转指令,跳转后不考虑返回,一旦执行B指令,ARM处理器直接跳转到指定目标地址

    _start:
    
        ldr sp,=0x80200000  @设置堆栈指针
        b main              @跳转到main函数

    上面的代码就是用来初始化C语言的运行环境,然后跳转到C底main函数处。这里跳转到main函数后不会再回到汇编,所以使用了B指令。

    BL指令

    BL指令和B指令相比,就是在LR(R14)中保存PC(R15)的值,看下图,处理器在各种模式下R0~R15各个内核寄存器作用,后面会大致提到。主要是这个R15,也就是PC,保存了当前指令地址加8个字节。也就是说R15记录了当前程序的位置,我们需要回到跳转前位置只需要读取LR到值就可以了。这个后面有机会可以详细讲讲。

    上面就是我们常用的指令,除此之外还有算数运算指令、逻辑运算指令等, 需要用到话我们再来补充。

  • 相关阅读:
    一次网络IO优化的讨论
    服务器框架回顾
    一个小工具:DebugFile
    TPO-23 C2 Advice on choosing courses
    TPO-23 C1 Post a student announcement
    TPO-22 C2 Revise a music history paper
    TPO-22 C1 Complain about a biased article
    TPO-21 C2 Which elective courses to take
    TPO-20-Apply for the undergraduate research fund
    TPO-21 C1 Find a building for orientation
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/15771639.html
Copyright © 2011-2022 走看看