内核汇编基础——ARM汇编指令详解

2022-12-09  

ARM的编程模式和七种模式

【文章福利】小编推荐自己的Linux内核源码交流群:【点击链接加入群聊869634926】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前50名可进群领取,并额外赠送一份价值798的内核资料包(含视频教程、电子书、实战项目及代码)!

点击下方链接即可免费领取内核相关学习资料哦

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

内核资料领取:Linux内核源码技术学习路线+视频教程内核源码


基本设定

  • 架构(32位)

    • 约定

      • Byte(字节):8bits

      • Halfword(半字) :16 bits (2 byte)

      • Word(字):32bits(4 byte)

  • 指令集

    • ARM指令集(32-bit)

    • Thumb指令集(16-bit)

    • Thunmb指令集(16&32bit)不考虑

工作模式

  • 种类:七种

  • 非特权模式(Normal:普通模式)

    • User(用户模式):非特权模式,大部分时候在这个模式下工作

  • 特权模式(Privilege:特权模式)

  • FIQ(快速中断模式):当一个高优先级(fast)中断产生时将会进入这种模式

  • IRQ(普通中断模式):当一个低优先级(normal)中断产生时进入这种模式

  • SVC(管理模式):当复位或软中断指令执行时进入

  • Abt(数据访问终止模式):当存取异常时进入

  • und(未定义指令终止模式):当执行未定义指令进入

  • sys(系统模式):使用和User模式相同的寄存器的特权模式

Privilege除了System模式外,其他5种为异常模式
各种模式的切换,程序员通过代码切换,(CPSR寄存器);也可以CPU在某些情况下自动切换(中断或者按复位键)
  • 为什么要有这么多模式?
    操作系统有安全级别要求,多模式为了方便操作系统多种角色安全等级需求

ARM寄存器组织

ARM 处理器有 37 个 32 位长的寄存器。

  • 1 个用作 PC(程序指针)。

  • 1 个用作 CPSR(程序状态寄存器)。

  • 5 个用作 SPSR(备份程序状态寄存器)。

  • 30 个用作通用寄存器。

注意:System模式使用user模式寄存器集
  • CPSR程序状态寄存器

  • 条件位

  • N:Negative result from ALU (ALU运算时负结果则置1)

  • Z:Zero result from ALU (ALU运算时零结果则置1)

  • C:ALU operation Carried out(进位标志位则置1)

  • V:ALU operation Carried out(溢出是则置1)

  • Mode位(理论上可以有32种模式)

    • 实际ARM只有7种工作模式(每种模式值可查)

  • T位(处理器状态控制位)

  • T = 0:处理器处于ARM状态(默认)

  • T = 1:处理器处于Thumb状态

  • 中断禁止位:

    • I = 1:禁止 IRQ

    • F = 1:禁止FIQ(快速中断)

ARM异常向量表

  • 异常:正常工作之外的流程都叫异常,中断是异常的一种。

异常会打断正在执行的工作,并且一般我们希望异常处理完成后继续回来执行原来的工作。

  • 异常向量表

    • 所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的。

    • 当异常发生时,CPU会自动动作(PC跳转到异常向量处处理异常,有时伴有一些辅助动作)。

    • 异常向量表是硬件向软件提供的处理异常的支持。

异常处理机制(处理过程)

  • 产生异常时,ARM内核

  1. 拷贝 CPSR 到 SPSR_

  2. 设置适当的CPSR位

  • 改变处理器状态进入 ARM 态

  • 改变处理器模式进入相应的异常模式

  • 设置中断禁止位禁止相应中断 (如果需要)

  • 保存返回地址到 LR_(R14)

  1. 设置 PC 为相应的异常向量

  • 从异常返回时

  1. 从 SPSR_恢复CPSR

  2. 从LR_恢复PC

注意:这些操作只能在 ARM 态执行。

异常处理中有一些是硬件自动做的,有一些是程序员需要自己做的。需要搞清楚哪些是需要自己做的,才知道如何写代码。
以上说的是CPU设计时提供的异常向量表,一般成为一级向量表。有些CPU为了支持多个中断,还会提供二级中断向量表,处理思路类似于这里说的一级中断向量表。

ARM汇编指令集

  • 指令与伪指令(汇编)

    • 指令:指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。

    • 伪指令:伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。

  • 两种风格

    • ARM官方的指令风格:指令一般用大写,一般用于Windows的开发环境(ADS,MDK等)如: LDR R0, [R1]。

    • GNU风格:指令一般用小写字母、linux中常用。如:ldr r0, [r1]。

  • ARM汇编特点

  1. LDR/STR架构

  • ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。

  • ldr(load register)指令将内存内容加载入通用寄存器。

  • str(store register)指令将寄存器内容存入内存空间中。

  • ldr/str组合用来实现 ARM CPU和内存数据交换

  1. 八种寻址方式

  • 寄存器寻址 mov r1, r2 r2的值赋值给r1

  • 立即寻址 mov r0, #0xFF00 #后面的数值直接赋值给r0

  • 寄存器移位寻址 mov r0, r1, lsl #3(书本无) r1中的数值左移三位,然后赋值给r0(就是乘于8)

  • 寄存器间接寻址 ldr r1, [r2] 类似于指针,r2中存操作数的地址,[]类似于解引用

  • 基址变址寻址 ldr r1, [r2, #4] r2为基址,r2里面的地址加4,这个地址存的值读到寄存器中

  • 多寄存器寻址 ldmia r1!, {r2-r7, r12} r1中的8个地址读到r2-r7和r12中(类似于数值中的8个元素)

  • 堆栈寻址 stmfd sp!, {r2-r7, lr} 将寄存器列表中的寄存器(R2到R7,lr)存入堆栈

  • 相对寻址 beq flag

  1. 指令后缀

同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:

  • B(byte)功能不变,操作长度变为8位。

  • H(half word)功能不变,长度变为16位。

  • !如果指令地址表达式中不含“!”后缀,则基址寄存器中的地址不会发生变化,指令中含有则变化,变化结果如下:基址寄存器中的值(指令执行后)=指令执行前的值+地址偏移量
    注意:

  • “!”后缀必须紧跟在地址表达式后面,而地址表达式要有明确的地址偏移量。

  • “!”后缀不能用于R15(PC)的后面

  • 当用于单个寄存器后面时,必须确性这个寄存器有隐性的偏移量,eg:“STMDB SP!,{R3,R5,R7}”此时地址基址寄存器SP的隐性偏移量是4。

  • S(signed)功能不变,操作数变为有符号,如 ldr ldrb ldrh ldrsb ldrsh。

  • S(S标志)功能不变,影响CPSR标志位,如 mov和movs movs r0, #0。指令中使用“S”后缀,指令执行后状态寄存器的条件标志位将被刷新;不使用“S”后缀时,指令执行后状态寄存器的条件标志位不会发生变化。此标志经常用于对条件进行测试,例如:是否溢出,是否进位等;根据这些变化,就可以进行一些判断,是否大于,是否相等,从而可能影响指令执行顺序。

两个S用于不同的指令,名称相同,但是在不同的指令结合却有不同的作用

条件执行后缀

 **注意**

 - 条件后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果。
 - 条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。

多指令流水线

为增加处理器指令流的速度,ARM使用多级流水线.,下图为3级流水线工作原理示意图。(ARM11为8级),当处理器执行简单的数据处理指令时,流水线使得平均每个时钟周期能完成 1 条指令。但 1 条指令需要 3个时钟周期来完成,因此,有 3 个时钟周期的延时( latency),但吞吐率( throughput)是每个周期一条指令。

 **注意**:PC指向正被取指的指令,而非正在执行的指令

常用ARM指令

  1. 数据处理指令

  • 数据传输指令 mov mvn

  • 算术指令 add sub rsb adc sbc rsc

  • 逻辑指令 and orr eor bic

  • 比较指令 cmp cmn tst teq

  • 乘法指令 mvl mla umull umlal smull smlal

  • 前导零计数 clz


  1. CPSR访问指令

  • mrs & msr

  1. mrs用来读psr,msr用来写psr

  2. CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。


  1. 跳转(分支)指令

  • b & bl & bx

  1. b 直接跳转(就没打开算返回)

  2. bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用

  3. bx跳转同时切换到ARM模式,一般用于异常处理的跳转。


  1. 访存指令

  • ldr/str & ldm/stm & swp

  1. 单个字/半字/字节访问 ldr/str

  2. 多字批量访问 ldm/stm

swp r1, r2, [r0] 将 r0 所指向的存储器中的字数据传输到 r1,同时将 r2中的字数据传输到 r0 所指向的内存单元。

swp r1, r1, [r0] 该指令完成将 r0 所执行的存储器中的子数据与 r1 中的字数据互换。

ARM汇编中的立即数

ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。

  • 合法立即数:并不是所有的 32-bit 立即数都是可以使用的合法立即数。只有那些通过将一个 8-bit 的立即数循环右移偶数位可以得到的立即数才可以在指令中使用。(了解即可

注意:加载立即数一般采用伪指令 ldr, 编译器会自动处理非法立即数,这里了解即可,

  1. 软中断指令

  • swi(software interrupt)
    软中断指令用来实现操作系统中系统调用(真实使用场景用处不大,仅作学习使用)

  • 协处理器(CP15)操作指令

  • CP15,即通常所说的系统控制协处理器( System Control Coprocesssor)。 SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。

    ARM 处理器支持 16 个协处理器。在程序执行过程中,每个协处理器忽略属于 ARM 处理器和其他协处理器的指令。当一个协处理器硬件不能执行属于它的协处理器指令时,将产生一个未定义指令异常中断,在该异常中断处理程序中,可以通过软件模拟该硬件操作。(不必深究

    • mcr & mrc

    1. mrc用于读取CP15中的寄存器

    2. mcr用于写入CP15中的寄存器

  • 使用方法

  • mcr{} p15, , , , , {}

    1. opcode_1:对于cp15永远为0

    2. Rd:ARM的普通寄存器

    3. Crn:cp15的寄存器,合法值是c0~c15

    4. Crm:cp15的寄存器,一般均设为c0

    5. opcode_2:一般省略或为0

    • 举例

    1. mrc p15, 0, r0, c1, c0, 0 该指令将协处理器 p15 的寄存器中的数据传送到ARM处理器的寄存器中

    2. mcr p15, 0, r0, c1, c0, 0 该指令将ARM处理器寄存器 r0 中的数据传送到协处理器 p15 的寄存器 c1 和 c0 中。

    协处理器的学习要点
    不必深究
    只看一般用法,不详细区分参数细节,否则会陷入很多复杂未知中。关键在于理解,而不在于记住。

    批量数据加载存储指令(LDM/STM与栈的处理)

    • 为什么需要多寄存器访问指令
      ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ld

    stm/ldm

    1. ldm(load register mutiple) 加载多个寄存器

    2. stm(store register mutiple) 存储多个寄存器

    举例:

    stmia sp, {r0 - r12}

    将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放(0x3001030),指令完成。

    一个访存周期同时完成13个寄存器的读写

    • 八种后缀

    1. ia(increase after)先传输,再地址+4

    2. ib(increase before)先地址+4,再传输

    3. da(decrease after)先传输,再地址-4

    4. db(decrease before)先地址-4,再传输

    5. fd(full decrease)满递减堆栈

    6. ed(empty decrease)空递减堆栈

    7. fa(·······) 满递增堆栈

    8. ea(·······)空递增堆栈

    • 四种栈

    1. 空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出

    2. 满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针

    3. 增栈:栈指针移动时向地址增加的方向移动的栈

    4. 减栈:栈指针移动时向地址减小的方向移动的栈

    后缀符号的作用

    1. 的作用
      ldmia r0, {r2 - r3}
      ldmia r0!, {r2 - r3}
      感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。

    2. ^ 的作用
      ldmfd sp!, {r0 - r6, pc}
      ldmfd sp!, {r0 - r6, pc}^
      ^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。

    总结:批量读取或写入内存时要用ldm/stm指令各种后缀以理解为主,不需记忆,最常见的是stmia(空堆栈递增)和stmfd(满堆栈递减)
    谨记:操作栈时使用相同的后缀(LDM/STM)就不会出错,不管是满栈还是空栈、增栈还是减栈。

    ARM汇编伪指令

    • 伪指令的意义

    1. 伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。

    2. 伪指令的意义在于指导编译过程。

    3. 伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。

    • GUN汇编中的一些符号

    1. @ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似。

    2. # 做注释,一般放在行首,表示这一行都是注释而不是代码。

    3. :以冒号结尾的是标号。

    4. . 点号在gnu汇编中表示当前指令的地址。

    5. # 立即数前面要加#或 $,表示这是个立即数。

    • 常用GUN伪指令

    1. .global _start @ 给_start外部链接属性

    2. .section .text @ 指定当前段为代码段

    3. .ascii .byte .short .long .word

    4. .quad .float .string @ 定义数据

    5. .align 4 @ 以16字节对齐

    6. .balignl 16 0xabcdefgh @ 16字节对齐填充

    7. .equ @ 类似于C中宏定义

    • 偶尔用到的GUN伪指令

    1. .end @标识文件结束

    2. .include @ 头文件包含

    3. .arm / .code32 @声明以下为arm指令

    4. .thumb / .code16 @声明以下为thubm指令

    • 最重要的几个伪指令

    1. ldr 大范围的地址加载指令

    2. adr 小范围的地址加载指令

    3. adrl 中等范围的地址加载指令

    4. nop 空操作

    adr与ldr

    • adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;

    • adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址

    • 在哪里

    • ldr加载的地址和链接时给定的地址有关,由链接脚本决定。

    ARM中有一个ldr指令,还有一个ldr伪指令
    一般都使用ldr伪指令而不用ldr指令


    文章来源于:电子工程世界    原文链接
    本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。