zoukankan      html  css  js  c++  java
  • ARM汇编基础知识

    1、前言

    汇编语言是一种低级编程语言,通常是一对一的汇编语言指令(助记符)与由核心执行的实际二进制操作码之间的关系,在高度优化的情况下,汇编代码可能会很有用,在编写编译器或者无法直接使用底层功能的情况下,在C中添加汇编代码是必需的,部分SoC的启动代码、设备驱动程序或者操作系统开发也可能需要汇编代码,在进行嵌入式Linux开发的时候需要掌握一定的ARM汇编知识,对于ARM Cortex-A架构的芯片,系统上电后,C语言运行环境还没有设置好,因此肯定是不能直接运行C代码的,所以必须先用汇编语言设置好C运行环境,初始化好SP指针等,使用汇编语言设置好C运行环境后,才能开始运行C语言代码。

    2、GNU汇编语法

    对于ARM架构下的汇编语言,编译使用的是gcc交叉编译工具链,汇编代码要符合GNU汇编语法,GNU汇编语法适用于所有的架构,并不是ARM独享的,GNU汇编由一系列的语句组成,每行一条语句,每条语句有3个可选部分,如下所示:

    label:    instruction    @comment

    label:label就是标号,表示地址的位置,有一些指令的前面可能会存在标号,然后通过这个标号就可以得到指令的地址,标号也可以用来表示数据的地址,任何以冒号":"结尾的标识符都会被认为是一个标号label;

    instruction:汇编指令或者伪指令;

    @:表示后面的是注释,和C语言的注释一样,同样也可以使用"/**/"进行注释;

    comment:要注释的内容。

    例如,下面的ARM汇编代码:

    Code:
        MOVS    R0,#0x14    @设置R0=0x14

    上面的举例代码中,"Code:"就是标号,"MOVS  R0,#0x14"就是指令,"@"后面就是注释的内容。

    需要注意的是,ARM中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混合使用。

    另外,用户还可以使用".section"伪操作来定义一个段,汇编系统中预定义了一些段名,如下:

    .text:表示代码段;

    .data:表示初始化的数据段;

    .bss:表示未初始化的数据段;

    .rodata:表示只读数据段。

    用户可以使用".section"来定义一个段,每个段以段名开始,以下一个段名或者文件结束,例如:

    .section    .mysection    @定义一个.mysection段

    汇编程序的默认入口标号是"_start",不过也可以在链接脚本中使用ENTRY来指明其它的入口点,下面的代码就是使用"_start"来作为入口标号:

    .global _start
    _start:
        MOVS    R0,#0X14    @R0=0x14

    在上面的代码中,".global"就是伪操作,表示"_start"是一个全局标号,类似C语言定义的全局变量一样,ARM汇编中常用的伪操作有如下:

    .byte:定义单字节数据,例如:.byte 0x14;

    .short:定义双字节数据,例如:.short 0x1000;

    .long:定义四字节数据,例如:.long 0x10001000;

    .equ:赋值语句,其格式为:.equ 变量名,表达式,例如:.equ cnt,0x14,表示cnt=0x14;

    .align:表示数据字节对齐,例如:.align 4,表示4字节对齐;

    .end:表示汇编源文件结束;

    .global:定义一个全局标号。

    GNU汇编同样也支持函数,函数的格式如下所示:

    函数名:
        函数体
        返回语句

    GNU汇编函数中的返回语句并不是必须的。

    3、ARM v7-A常用汇编指令

    接下来,总结一些ARM v7-A架构中常用的汇编指令,如下:

    (1)处理器内部数据传输

    在处理器内部来回传递数据,常见的操作有:

    • 数据从一个寄存器传输到另一个寄存器
    • 数据传输到特殊寄存器,例如CPSR寄存器
    • 将立即数传输到寄存器

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

    指令 目的 作用
    MOV R0 R1 将R1里面的数据赋值到R0中
    MRS R0 CPSR 将CPSR里面的数据赋值到R0中
    MSR CPSR R1 将R1的数据赋值到CPSR中

    (1.1)MOV指令

    MOV指令用于将数据从一个寄存器赋值到另一个寄存器,或将一个立即数赋值到寄存器里面,使用示例如下:

    MOV    R0,R1      @将R1中的数据传递到R0,也就是R0=R1
    MOV    R0,#0x14    @将立即数0x14传递到R0,也就是R0=0x14

    (1.2)MRS指令

    MRS指令用于将特殊寄存器,例如CPSR或SPSR中的数据传递到通用寄存器,使用如下:

    MRS    R0,CPSR    @将CPSR中的数据传递到R0,也就是R0=CPSR

    (1.3)MSR指令

    MSR指令用来将通用寄存器中的数据传递到特殊寄存器,例如CPSR和CPSR,使用如下:

    MSR    CPSR,R0    @将R0的数据传递到CPSR中,也就是CPSR=R0

    (2)存储器访问指令

    ARM架构不能直接去访问存储器,例如RAM中的数据,I.MX6UL中的寄存器就是RAM类型的,当我们使用汇编指令来配置SoC寄存器的时候就需要借助存储器访问指令,一般的步骤为,先将要配置的寄存器值写入到Rx寄存器中,然后使用存储器访问指令将Rx中的数据写入到SoC的寄存器中,读取SoC寄存器的值类似,常用的存储器访问指令有LDR和STR,用法如下:

    指令 作用
    LDR Rd,[Rn,#offset] 将存储器Rn+offset位置的数据读取到Rd中
    STR Rd,[Rn,#offset] 将Rd中的数据写入到存储器Rn+offset位置中

    (2.1)LDR指令

    在嵌入式ARM中,LDR指令用于从存储器中加载数据到通用寄存器Rx中,另外,LDR指令也能将一个立即数加载到寄存器Rx中,但是要使用"=",而不是"#",在ARM开发中,LDR最常用的就是读取SoC的寄存器值,例如,I.MX6UL有一个寄存器GPIO1_GDIR,该寄存器地址为0x0209C004,如果想要读取该寄存器中的数值,可以使用下面汇编代码:

    LDR    R0,=0x0209C004    @将寄存器地址0x0209C004加载到R0中
    LDR    R1,[R0]    @读取寄存器地址0x0209C004中的数据到R1中

    上述示例代码中,没有使用到offset,也就是offset为0。

    (2.2)STR指令

    LDR指令可以用来读取存储器的数据都Rx寄存器中,STR指令可以将数据写入到存储器中,例如,I.MX6UL有一个寄存器GPIO1_GDIR,该寄存器地址为0x0209C004,如果想要往该寄存器中写入数值,可以使用下面汇编代码:

    LDR    R0,=0x0209C004    @将寄存器地址0x0209C004加载到R0中
    LDR    R1,=0x00000001    @将0x00000001加载到R1中
    STR    R1,[R0]    @将R1中的值写入到寄存器地址0x0209C004中

    另外,需要注意的是,LDR指令和STR指令都是按照字进行读取和写入的,也就是直接操作32bit数据,如果要按照字节或者半字操作的话,可以在LDR指令和STR指令后面加上"B"或"H",例如按字节操作的指令为LDRB和STRB。

    (3)压栈和出栈指令

    在编写代码的时候,通常会在A函数中调用B函数,当B函数执行完以后需要再回到A函数处执行,如果想要跳回到A函数继续正常执行,那就必须在跳到B函数之前将当前处理器的状态保存起来(保存R0~R15寄存器的值),当B函数执行完后,需要将前面保存的寄存器值恢复到R0~R15,保存寄存器的值操作就是现场保护,恢复寄存器的值操作就是恢复现场,在进行现场保护需要使用PUSH指令进行压栈操作,恢复现场就需要使用POP指令进行出栈操作,这些指令一次可以操作多个寄存器数据,利用当前的栈指针SP来生成地址,用法如下:

    指令 作用
    PUSH <reg list> 将寄存器列表压入栈中
    POP <reg list> 将寄存器列表出栈

    例如,现在需要将R0~R3寄存器和R12这5个寄存器压栈,当前的SP指针指向0x80000000,处理器的堆栈向下增长,ARM汇编代码如下:

    PUSH    {R0~R3,R12}    @将R0~R3和R12进行压栈操作

    代码执行后,堆栈如下所示:

    此时的堆栈指针SP指向0x7FFFFFEC,假如现在需要将LR寄存器进行压栈,ARM汇编代码如下:

    PUSH    {LR}    @将LR寄存器进行压栈操作

    代码执行后,堆栈生长如下所示:

    想要将寄存器出栈的话,使用下面的ARM汇编代码:

    POP    {LR}    @先将LR寄存器出栈
    POP    {R0~R3,R12}    @将R0~R3,R12寄存器出栈

    栈是一种先进后出的模型,出栈是从栈顶先开始,地址依次减小来提取堆栈中的数据恢复到寄存器列表中。

    (4)跳转指令

     在ARM汇编中,有多种跳转操作,例如:

    • 使用B、BL或BX指令直接跳转
    • 直接向PC寄存器里面写入数据

    在上面列出的操作中,都可以完成跳转操作,但是一般常用的还是使用跳转指令B、BL或者BX,指令用法如下:

    指令 作用
    B <label> 跳转到label
    BX <Rm> 间接跳转,跳转到存放在Rm的地址处,并切换指令集
    BL <label> 跳转到label,并将返回地址保存到LR中
    BLX <label> 跳转到Rm指定的地址,并将返回地址保存到LR中,切换指令集

    (4.1)B指令

    B指令会将PC寄存器的值设置为跳转的目标地址,一旦执行B指令后,ARM处理器将会立即跳到指定的目标地址,如果想调用的函数不会再返回到原来的地方执行,就可以使用B指令,使用示例如下:

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

    示例代码就是在汇编中初始化C运行环境,然后跳转到C文件的main函数处执行,main函数调用后,将不会返回到原来的位置处执行。

    (4.2)BL指令

    BL指令在跳转之前会将当前PC寄存器的值保存到LR寄存器中,通过将LR寄存器中的值重新加载到PC中,就可以继续从跳转之前的代码处执行,这是子程序调用的一个基本常用手段,例如,ARM处理器的irq中断服务函数使用汇编编写,主要是使用汇编来实现现场保护和现场恢复、获取中断号等,具体的中断处理过程使用C函数,所以存在在汇编中调用C函数的问题,C函数的处理过程完成以后,需要返回到汇编的中断服务函数,一般是恢复现场,这时候就要使用BL指令进行跳转了,典型代码如下:

    push    {r0, r1}    @将r0和r1进行保存
    cps    #0x13    @处理器进入SVC模式
    
    bl    system_irqhandler    @跳转到C的中断处理函数
    
    cps    #0x12    @处理器进入IRQ模式
    pop    {r0, r1}    @恢复r0和r1寄存器
    str    r0, {r1, #0x10 }    @中断执行完成,写EOIR

    上面代码中,使用BL指令跳转到C版本的中断处理函数system_irqhandler,函数执行完后,需要返回到原来的位置继续执行下面的汇编代码。    

    (5)算术运算指令

    ARM汇编中也可以进行算术运算,例如加减乘除操作,使用对应的运算指令即可,常用的运算指令用法如下:

    指令 计算公式 作用
    ADD Rd, Rn, Rm Rd = Rn + Rm 加法运算
    ADD Rd, Rn, #immed Rd = Rn + #immed
    ADC Rd, Rn, Rm Rd = Rn + Rm + 进位 带进位的加法运算
    ADC Rd, Rn, #immed Rd = Rn + #immed + 进位
    SUB Rd, Rn, Rm Rd = Rn - Rm 减法运算
    SUB Rd, Rn, #immed Rd = Rn - #immed
    SBC Rd, Rn, Rm Rd = Rn - Rm - 借位 带借位的减法
    SBC Rd, Rn, #immed Rd = Rn - #immed -借位
    MUL Rd, Rn, Rm Rd = Rn * Rm 乘法运算
    UDIV Rd, Rn, Rm Rd = Rn / Rm 无符号除法运算
    SDIV Rd, Rn, Rm Rd = Rn / Rm 有符号除法运算

    (6)逻辑运算指令

     ARM中还有一些常用的逻辑运算指令,用法如下:

    指令 计算公式 作用
    AND Rd, Rn Rd = Rd & Rn 按位与
    AND Rd, Rn, #immed Rd = Rn & #immed
    AND Rd, Rn, Rm Rd = Rn & Rm
    ORR Rd, Rn Rd = Rd | Rn 按位或
    ORR Rd, Rn, #immed Rd = Rn | #immed
    ORR Rd, Rn, Rm Rd = Rn | Rm
    BIC Rd, Rn Rd = Rd & (~Rn) 位清除
    BIC Rd, Rn, #immed Rd = Rn & (~#immed)
    BIC Rd, Rn, Rm Rd = Rn & (~Rm)

    对于更多更详细的ARM汇编指令,可以参考ARM官网的相关文档。

    4、小结

    本文主要记录了ARM中一些汇编基础知识和一些常用的ARM汇编指令。

  • 相关阅读:
    CentOS虚拟机和物理机共享文件夹实现
    集训第六周 数学概念与方法 概率 数论 最大公约数 G题
    集训第六周 数学概念与方法 概率 F题
    集训第六周 E题
    集训第六周 古典概型 期望 D题 Discovering Gold 期望
    集训第六周 古典概型 期望 C题
    集训第六周 数学概念与方法 UVA 11181 条件概率
    集训第六周 数学概念与方法 UVA 11722 几何概型
    DAG模型(矩形嵌套)
    集训第五周 动态规划 K题 背包
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12384882.html
Copyright © 2011-2022 走看看