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汇编指令。

  • 相关阅读:
    TPM Key相关概念
    (转)eclipse报错及解决说明 "XX cannot be resolved to a type "
    (转)Bat Command
    (转)Linux下查看文件和文件夹大小 删除日志
    (转)每天一个linux命令(50):crontab命令
    (转)Vi命令详解
    (转)maven打包时跳过测试
    (转)mybatis常用jdbcType数据类型
    (转)MyBatis在插入的数据有空值时,可能为空的字段都要设置jdbcType
    (转)mybatis:动态SQL
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12384882.html
Copyright © 2011-2022 走看看