• ARM 汇编基础知识


    1.为什么学习汇编?

            我们在进行嵌入式 Linux 开发的时候是绝对要掌握基本的 ARM 汇编,因为 Cortex-A 芯片一
    上电 SP 指针还没初始化, C 环境还没准备好,所以肯定不能运行 C 代码,必须先用汇编语言设置好 C 环境,比如初始化 DDR 、设置 SP 指针等等,当汇编把 C 环境设置好了以后才可以运行 C 代码。
            我们要编写的是 ARM 汇编,编译使用的 GCC 交叉编译器,所以我们的汇编代码要符合 GNU 语法。

     2.GNU 语法

    1.语法结构

            GNU 汇编语法适用于所有的架构,并不是 ARM 独享的, GNU 汇编由一系列的语句组成,
    每行一条语句,每条语句有三个可选部分:
            
    label:instruction @ comment
    • label 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识 符都会被识别为一个标号。
    • instruction 即指令,也就是汇编指令或伪指令。@符号,表示后面的是注释,就跟 C 语言里面的“/*”和“*/”一样,其实在 GNU 汇编文件中我们也可以使用“/*”和“*/”来注释。
    • comment 就是注释内容。

    for example:

    1. add: @标号
    2. MOVS R0, #0X12 @指令:设置 R0=0X12
            注意!ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用
    小写,但是不能大小写混用

    2.section 伪操作

    用户可以使用 .section 伪操作来定义一个段,汇编系统预定义了一些段名:
    .text 表示代码段。
    .data 初始化的数据段。
    .bss 未初始化的数据段。
    .rodata 只读数据段。
    也可以自己定义段,每个段以段名开始,以下一段名或者文件结尾结束
    .section .testsection   @定义一个 testsetcion 段

    3.汇编程序入口

            汇编程序的默认入口标号是_start ,不过我们也可以在链接脚本中使用 ENTRY 来指明其它
    的入口点,下面的代码就是使用 _start 作为入口标号:
    1. /*.global是伪操作,表示_start是全局标号*/
    2. .global _start
    3. _start:
    4. ldr r0, =0x12 @r0=0x12

    类似C语言,常见的伪操作有:

    .byte     定义单字节数据,比如.byte 0x12
    .short   定义双字节数据,比如.short 0x1234
    .long    定义一个 4 字节数据,比如 .long 0x12345678
    .equ     赋值语句,格式为:.equ 变量名,表达式,比如 .equ num, 0x12 ,表示 num=0x12
    .align    数据字节对齐,比如: .align 4 表示 4 字节对齐。
    .end     表示源文件结束。
    .global   定义一个全局符号,格式为: .global symbol ,比如: .global _start

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

    函数名 :
            函数体
            返回语句  @GNU 汇编函数返回语句不是必须的

     for example:

    1. /* SVC 中断 */
    2. SVC_Handler: @函数名
    3. ldr r0, =SVC_Handler @函数体
    4. bx r0 @返回语句

    3.Cortex-A7 常用汇编指令

    3.1 处理器内部数据传输指令

    常用:MOVMRS MSR 

    1.MOV指令

    将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄 存器里面
     
    1. MOV R0,R1
    2. @将寄存器 R1 中的数据传递给 R0,即 R0=R1
    3. MOV R0, #0X12
    4. @将立即数 0X12 传递给 R0 寄存器,即 R0=0X12

    2.MRS指令

    将特殊寄存器 ( CPSR SPSR) 中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令!
    MRS R0, CPSR @将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR

    3.MSR指令

    MSR 指令和 MRS 刚好相反, MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就
    是写特殊寄存器,写特殊寄存器只能使用 MSR。
    MSR CPSR, R0 @将 R0 中的数据复制到 CPSR 中,即 CPSR=R0

    3.2 存储器访问指令

            ARM 不能直接访问存储器,比如 RAM 中的数据, I.MX6UL 中的寄存器就是 RAM 类型 的,我用汇编来配置 I.MX6UL 寄存器的时候需要借助存储器访问指令,一般先将要配置的值写入到 Rx(x=0~12) 寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到 I.MX6UL 寄存器。读取 I.MX6UL 寄存器也是一样的,只是过程相反。
            常用的存储器访问指令有两种:LDR 和 STR。
    1.LDR指令
            LDR 主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx 中,LDR 加载立即数的时候要使用“ = ”,而不是“ # ”。
            在嵌入式开发中,LDR 最常用的就是读 取 CPU 的寄存器值。
    例如:如 I.MX6UL 有个寄存器 GPIO1_GDIR,其地址为 0X0209C004,我们现在要读取这个寄存器中的数据,代码: 
    1. LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
    2. LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中,offset为0
    2.STR指令
            LDR 是从存储器读取数据, STR 就是将数据写入到存储器中,同样以 I.MX6UL 寄存器
    GPIO1_GDIR 为例:
    1. LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
    2. LDR R1, =0X20000002 @R1 保存要写入到寄存器的值,即 R1=0X20000002
    3. STR R1, [R0] @将 R1 中的值写入到 R0 中所保存的地址中
    LDR 和 STR 都是按照进行读取和写入的,也就是操作的 32 位数据,如果要按照字节、 半字进行操作的话可以在指令“LDR ”后面加上 B H ,比如按字节操作的指令就是 LDRB 和 STRB,按半字操作的指令就是 LDRH STRH

    3.3压栈和出栈指令

            我们通常会在 A 函数中调用 B 函数,当 B 函数执行完以后再回到 A 函数继续执行。要想
    再跳回 A 函数以后代码能够接着正常运行,那就必须在跳到 B 函数之前将当前处理器状态保存
    起来 ( 就是保存 R0~R15 这些寄存器值 ) ,当 B 函数执行完成以后再用前面保存的寄存器值恢复
    R0~R15 即可。保存 R0~R15 寄存器的操作就叫做现场保护恢复 R0~R15 寄存器的操作就叫做
    恢复现场在进行现场保护的时候需要进行压栈 (入栈) 操作,恢复现场就要进行出栈操作压栈
    的指令为 PUSH,出栈的指令为 POPPUSH POP 是一种多存储和多加载指令即可以一次
    操作多个寄存器数据他们利用当前的栈指针 SP 来生成地址 PUSH POP 的用法如表:

     例如:将R0~R3、R12寄存器压栈操作当前的 SP 指针指向 0X80000000,处理器的堆栈是向下增长的,使用的汇编代码如下:

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

    压栈完成以后的堆栈如图

     由于32位处理器,每个寄存器为32位,占用4个字节,这里5个寄存器占用20个字节,转换为十六进制是0x14,需要在堆栈上分配连续的地址空间长度为0x14,

            故SP指针的位置变化为:0x800000 - 0x14 = 0x7FFFFFEC

     LR 进行压栈完成以后的堆栈模型如图:

    这里使用LR寄存器来存放程序的返回地址,同理, LR寄存器(备份寄存器R14)占4个字节,

    SP变化:0x7FFFFFEC-0X04 = 0X7FFFFFE8

    接下来作出栈操作:

    POP {LR}                @先恢复 LR
    POP {R0~R3,R12}  @ 在恢复 R0~R3,R12
     出栈的就是从栈顶,也就是 SP 当前执行的位置开始,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中。

            PUSH 和 POP 的另外一种写法是“STMFD SP!”和“LDMFD SP!”。STM LDM 就是多存储和多加载,可以连续的读写存储器中的多个连续数据。

    3.4跳转指令

    有多种跳转操作,比如:
    ①、直接使用跳转指令 B BL BX 等。
    ②、直接向 PC 寄存器里面写入数据。

    一般常用的还是 BBL 或 BX

    如果要在汇编中进行函 数调用使用的就是 B 和 BL 指令

    1 B 指令
            这是最简单的跳转指令,B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指 令,ARM 处理器就会立即跳转到指定的目标地址,如果要调用的函数不会再返回到原来的执行
    处,那就可以用 B 指令,例如:
      _start :                        @入口标号
     ldr sp ,= 0X80200000 @ 设置栈指针
     b main                        @跳转到 main 函数  跳转到 C 文件以后再也不会回到汇编了。
    2 BL 指令
            BL 指令相比 B 指令,在跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值所以可以 通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行这是子程序调用的一个基本但常用的手段。比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令。

    3.5算术运算指令

    掌握加法运算。

    3.6逻辑运算指令

    具体案例再补充。

    参考:

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6》

    ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》和《 ARM Cortex-A(armV7) 编程手册 V4.0.pdf

  • 相关阅读:
    js正则表达式系统讲解(全面的总结)
    APP启动流程一(源码30)之向Zygote发送创建APP进程的请求
    iOS开发者是钻研SwiftUI还是尝试接触Flutter好呢?
    多色柱状图让数据区分更明显更醒目
    不依赖vue实例,怎么实现一个eventBus?
    粽子产线的速度提升
    基于SSM的网上书店商城设计与实现
    file 文件与 base64 互相转化
    关于std::vector<std::string>的操作
    如何在 win系统 桌面上加入某个软件的快捷方式
  • 原文地址:https://blog.csdn.net/qq_42049394/article/details/132626234