Bootstrap

关于ARM和汇编语言

一图流

 ARM

计算机组成

  • 输入设备

  • 输出设备

  • 存储设备

  • 运算器

  • 控制器

处理器读取内存程序执行的过程

  • 取指阶段:控制器器通过地址总线向存储器发送想要获取的指令的地址编号,存储器将指定的指令发送给处理器

  • 译码阶段:控制器对指令进行分析和译码,确定当前指令的作用

  • 执行阶段:控制器通知运算器进行对应的指令运算

指令

  • 机器指令:我们为处理器的每一种运算设置了一条机器指令,机器指令又被称为机器码,由多个0和1组成

  • 汇编指令:将每一条汇编指令理解为一条机器指令的封装形式,当一条汇编指令被编译后,就得到一条机器指令,执行后让处理器进行对应的运算操作

指令集

概念

  • 指令的集合

  • 不同的指令集可以被当作设计不同处理器内核的架构

  • 一般在设计一个内核时会预先将这个处理器内核可以进行的运算的指令设计出来,把这些指令组成一个指令集,按照这个指令集设计这个内核

复杂指令集(CISC)

  • 看重当前处理器的处理能力,将所有能够进行的运算操作的指令组成一个指令集,这种指令集就是复杂指令集

  • 优点:功能强大

  • 缺点:功耗、成本、体积都高

  • 特点

    • 每一条指令的长度不固定

    • 每一条指令的执行周期也不固定

复杂指令集(CISC)

  • 追求功能的特定性,将使用频度最高,比较简单的指令组成指令集

  • 精简指令集优点:开发成本低、功耗低

  • 缺点:实现的功能简单

  • 特点

    • 指令的大小固定

    • 指令的执行周期固定

常见的精简指令集架构

  • ARM架构:ARM公司设计的精简指令集架构,使用的最多

  • RISC-V架构:开源,未来会火

  • MIPS架构:中国龙芯收购,完全垄断

ARM架构

  • ARMV1-ARMV6 //基本不被使用

  • ARMV7:32位处理器架构 支持32位指令集

  • ARMV8:64位处理器架构,支持64位指令集,向下兼容32位指令集

  • ARMV9:64位处理器架构,支持64位指令集

ARM v7架构处理器的工作模式

  • User : 非特权模式,大部分任务执行在这种模式

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

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

  • Supervisor(SVC) :当复位或软中断指令执行时将会进入这种模式

  • Abort : 当存取异常时将会进入这种模式

  • Undef : 当执行未定义指令时会进入这种模式

  • System : 使用和User模式相同寄存器集的特权模式

ARMv7架构的寄存器组织

概述

  • 不同工作模式下使用的内核中的寄存器是不一样的

  • 不算MON和HYP模式,用户可操作的寄存器总共有37

  • 每一个寄存器的大小都是4字节

寄存器

  • R0-R12用来保存基本的操作数据和运算的数据

  • R13寄存器

    • 栈指针寄存器(STACK POINTER/sp)

    • sp寄存器保存该模式下栈内存栈顶地址,用于进行栈内存读写

  • R14寄存器

    • 链接寄存器(link register/LR)

    • 链接寄存器是用于特定程序跳转场景下用于保存程序的返回地址

  • R15寄存器

    • 程序计数器(program counter/PC)

    • PC寄存器中保存马上要被取指的指令地址

    • 当PC保存的地址执行后,PC的数值会自动向下加一条指令的大小

    • 实现程序跳转的本质就是修改PC的值

  • SPSR寄存器(saved program status register)

    • 被保存的程序状态寄存器

    • 当处理器因为各种异常切换到异常模式工作时会将CPSR值提前保存到SPSR中,处理器处理完异常之后会将SPSR的值赋值给CPSR,用于恢复处理器的工作状态

  • CPSR寄存器(current program status register)

    • 记录当前程序状态寄存器

    • 工作状态中包含程序的工作模式、中断禁止位以及程序的运算结果条件位等信息

状态寄存器        

  • N[31] : 指令的运行结果为负数时,N位被自动置1,否则为0

  • Z[30] : 指令的运行结果为零时,Z位被自动置1,否则为0.

  • C[29] :

    • 加法:加法运算如果产生进位,C位被自动置1,否则为0.

    • 32位指令:低32位向高32位进位

    • 减法:减法运算如果产生借位,C位被自动清0,否则位1.

    • 32位指令:低32位向高32位借位

  • V[28] : 符号位发送变化,V位被自动置1,否则清0.

  • I[7] : IRQ中断屏蔽位

    • I = 0 : 不屏蔽IRQ中断

    • I = 1 : 屏蔽IRQ中断

  • F[6] : FIQ中断屏蔽位

    • F = 0 : 不屏蔽FIQ中断

    • F = 1 : 屏蔽FIQ中断

  • T[5] : 状态位

    • T = 0 : 表示ARM状态,执行的是ARM指令集

    • T = 1 : 表示Thumb状态,执行的是Thumb指令集

    • ARM指令集 : 一条汇编指令编译生成32位的机器码

    • thumb指令集:一条汇编指令编译生成16位的机器码

    • ARM指令集的代码的密度低,而thumb指令记得代码密度高

    • ARM指令集的功能性要高于Thumb指令集

  • M[4:0] : 模式位

    • 10000 User mode

    • 10001 FIQ mode;

    • 10010 IRQ

    • 10011 SVC mode;

    • 10111 Abort mode;

    • 11011 Undfined mode;

    • 11111 System mode;

    • 10110 Monitor mode;

数据汇编指令

基本格式

  • <opcode>{<cond>}{s} <Rd>, <Rn>, <shifter_operand>

  • 解释:如果满足 cond 条件,则让 Rn 和 shifter_operand 进行 opcode 操作,如果写了 s 则在运算结束后影响 CPSR 寄存器

  • 参数

    • opcode:指令功能码,表示当前指令

    • cond:条件码,可写可不写,在指令执行之前先判断条件是否满足,不满足则不执行

    • s:可写可不写,如果在指令码后面加了一个s,则进行运算时会影响到CPSR条件位

    • Rd:目标寄存器

    • Rn:第一操作寄存器

    • shifter_operand:第二操作数

  • 注意

    • 第一操作寄存器只能是寄存器,不能是数值

    • 第二操作数可以为寄存器,也可以为数值,如果为数值,需要在前面加上#

    • 汇编指令不区分大小写

数据处理指令

数据搬移指令

  • mov {条件码} 目标寄存器 操作数

    • 将操作数搬移到目标寄存器中

  • mvn {条件码} 目标寄存器 操作数

    • 将操作数先取反,再搬移到目标寄存器中

  • 注意

    • 以上两个操作指令只能搬移立即数

    • 如果需要搬移非立即数可以使用ldr

      • ldr 目标寄存器,=数据

      • 从内存中寻找=数据的值放到目标寄存器中

立即数

        一个32位指令空间中预留了12位保存操作数,可以通过某一个规则对操作数进行处理,将处理后的数值存放在这个12位空间中,所以处理完能够保存到12位空间中的数据就是立即数,否则不是

规则
  • 将操作数循环右移偶数位 ,如果能够得到一个0-255内的数据,就说明这个数据是一个立即数

  • 反过来,得到的数据循环右移偶数位,也可以得到操作数

  • 指令中操作数的12位空间分为低8位和高4位,低8bit保存循环右移得到操作数的那个0-255内的数字

  • 高4位保存0-255内的数据循环右移的偶数位/2

数据移位指令

  • lsl{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数据逻辑左移第二操作数位,然后放到目标寄存器

  • lsr{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数据逻辑右移第二操作数位,然后放到目标寄存器

  • ror{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数据循环右移第二操作数位,然后放到目标寄存器

  • 注意

    • 可以将其写成c语言风格

    • 例:LSL R1,R0,#(0X1<<2),及那个R0的数据逻辑左移2位放到R1中

位运算指令

  • and{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数和第二操作数进行与运算,然后放到到目标寄存器中

  • orr{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数和第二操作数进行或运算,然后放到到目标寄存器中

  • eor{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数和第二操作数进行异或运算,然后放到到目标寄存器中

  • mvn,按位取反

  • bic{条件码} 目标寄存器,第一操作寄存器,第二操作数

    • 将第一操作寄存器的数和第二操作数的取反值进行与运算,结果保存到目标寄存器中

算数运算符指令

  • add{条件码}{s} 目标寄存器,第一操作寄存器,第二操作数

    • 目标寄存器=第一操作寄存器+第二操作数

  • adc{条件码}{s} 目标寄存器,第一操作寄存器,第二操作数

    • 目标寄存器=第一操作寄存器+第二操作数+CPSR寄存器c位的值

  • sub{条件码}{s} 目标寄存器,第一操作寄存器,第二操作数

    • 目标寄存器=第一操作寄存器-第二操作数

  • sbc{条件码}{s} 目标寄存器,第一操作寄存器,第二操作数

    • 目标寄存器=第一操作寄存器-第二操作数-cpsr寄存器c位的取反值

  • mul{条件码}{s} 目标寄存器,第一操作寄存器,第二操作数

    • 目标寄存器=第一操作寄存器*第二操作数

  • 32位寄存器运算64位数

    • 32位处理器一条指令能运算32位数据,要进行64位数据运算,要多条指令

    • 遵守原则

      • 低32位进行运算,运算的结果影响到CPSR条件位

      • 高32位数据进行运算,运算结果考虑到cpsr条件位

比较指令

  • cmp 第一操作寄存器,第二操作数

    • 将第一操作数和第二操作寄存器进行比较,本质是进行减法运算,然后影响CPSR的位

    • 所以通常会跟这条件码混用

  • tst 目标寄存器,#(0X1<<N)

    • 这个指令用来判断目标寄存器的第N位是否为0,然后影响CPSR的Z位

  • TEQ 目标寄存器,第二操作数

    • 判断目标寄存器的值是否和第二操作数相等,并只能影响CPSR寄存器的z位,无法影响c和v位

跳转指令

  • b 标签

    • 跳转到指定的标签下,跳转后LR寄存器不保存程序返回地址

  • bl 标签

    • 跳转到指定的标签下,跳转后LR寄存器保存程序返回地址

内存读写指令

str

  • str 目标寄存器,[目标地址]

    • 将目标寄存器中4字节数据写入到目标地址对应的内存中

  • strh 目标寄存器,[目标地址]

    • 将目标寄存器中2字节数据写入到目标地址对应的内存中

  • strb 目标寄存器,[目标地址]

    • 将目标寄存器中1字节数据写入到目标地址对应的内存中

ldr

  • ldr 目标寄存器,[目标地址]

    • 从目标地址对应的内存中读取4字节数据保存到目标寄存器中

  • ldrh 目标寄存器,[目标地址]

    • 从目标地址对应的内存中读取2字节数据保存到目标寄存器中

  • ldrb 目标寄存器,[目标地址]

    • 从目标地址对应的内存中读取2字节数据保存到目标寄存器中

寄存器内存读写地址索引

前索引
  • str 目标寄存器,[基地址,偏移量]

    • 将目标寄存器的数据写入到基地址+偏移量的地址中

  • ldr 目标寄存器,[基地址,偏移量]

    • 从基地址+偏移量的地址中读取数据保存到目标寄存器中

后索引(数据先保存,地址再加加)
  • str 目标寄存器,[基地址],偏移量

    • 将目标寄存器的数据写入到基地址的内存中,然后基地址增加偏移量大小

  • ldr 目标寄存器,[基地址],偏移量

    • 从基地址内存中读取数据保存到目标寄存器中,,然后基地址增加偏移量大小

自动索引(地址先加加,再保存到地址)
  • str 目标寄存器,[基地址,偏移量]!

    • 先将基地址自增偏移量大小,再将目标寄存器的数据写入到基地址的内存中

  • ldr 目标寄存器,[基地址,偏移量]!

    • 先将基地址自增偏移量大小,再从基地址内存中读取数据保存到目标寄存器中

批量寄存器内存读写

基地址不变
  • stm 基地址,{寄存器列表}

    • 将寄存器列表中所有寄存器的数据都写入到基地址为首地址的内存中

  • ldm 基地址,{寄存器列表}

    • 从基地址开始往下读取数据,保存到寄存器列表中的每一个寄存器中

  • 注意

    • 寄存器列表可以不连续,但是他始终是从小到大写或读

基地址往大增长
先操作,在增长
  • stmia 基地址!,{寄存器列表}

    • 先向基地址对应的内存中写入一个数据,基地址往大地址方向增长

  • ldmia 基地址!,{寄存器列表}

    • 先读取基地址对应内存的数据,基地址往大地址方向增长

 先增长,再操作
  • stmib 基地址!,{寄存器列表}

    • 基地址往大地址方向增长 再向基地址对应的内存中写入一个数据

  • ldmib 基地址!,{寄存器列表}

    • 先基地址往大地址方向增长,再读取基地址对应内存的数据

基地址往小减少
先操作,再减少
  • stmda 基地址!,{寄存器列表}

    • 先向基地址对应的内存中写入一个数据,基地址往小地址方向增长

  • ldmda 基地址!,{寄存器列表}

    • 先读取基地址对应内存的数据,基地址往小地址方向增长

先减少,在操作
  • stmdb 基地址!,{寄存器列表}

    • 基地址往小地址方向增长 再向基地址对应的内存中写入一个数据

  • ldmdb 基地址!,{寄存器列表}

    • 先基地址往小地址方向增长,再读取基地址对应内存的数据

栈内存读写

栈区是一段指定好的内存空间,一般用来保存临时数据,SP寄存器保存栈顶地址

栈的分类
  • 空栈(EA):压栈完毕后,SP保存的栈顶地址空间中没有有效数据

  • 满栈(ED):压栈完毕后,SP保存的栈顶地址空间中有有效数据

  • 增栈(FA):每次压栈一个数据,SP保存的栈顶地址往大地址增长

  • 减栈(FD):每次压栈一个数据,SP保存的栈顶地址往小地址增长

  • ARM默认使用满减栈

栈的实现
  • 按照ARM默认的方式进行压栈和出栈

    • push {寄存器列表}

    • pop {寄存器列表}

  • 使用批量寄存器的命令实现

  • 使用栈的专属后缀

状态寄存器传送指令

  • MRS 目标寄存器,CPSR

    • 读取CPSR寄存器数值,保存到目标寄存器

  • MSR CPSR,操作数

    • 修改CPSR寄存器的数值为操作数

  • 注意

    • 可以在特权模式下通过修改CPSR数值切换到USER模式

    • 无法在useR模式下修改CPSR数值切换到特权模式,如果想要进入特权模式,需要发生对应的特权事件

异常产生指令

软中断

  • swi 操作数

    • 操作数是一个立即数,这个操作数就是产生的软中断的中断号

异常模式和异常源

  • 异常模式:当处理器发生异常后进入的工作模式

  • 异常源:引发处理器产生异常的源头

  • 五种异常模式,七种异常源

异常的处理过程

处理器发生异常后,会自动进行四大步三小步工作,处理异常

四大步三小步

  • 保存CPSR数值到对应异常模式的SPSR寄存器中

  • 修改CPSR数值

    • 修改模式位为对应的异常模式 [4:0]

    • 修改工作状态为ARM状态 [5]->0

    • 根据当前异常的优先级禁用中断[7:6]

  • 保存程序的返回地址到异常模式的LR寄存器中

  • 修改PC的值到对应异常在异常向量表中的位置

异常的返回(需要手动返回)

  • 将异常模式下的SPSR数值赋值给CPSR,恢复程序的状态

  • 将异常模式下LR的数值赋值给PC,恢复程序执行的位置

伪操作

  • 能够在程序编译过程中起到编译引导作用的内容

  • .text .global .if .else .endif.....

  • 伪操作不占用指令内存

伪指令

不是汇编指令,但是可以起到指令的作用,伪指令也会占用一定内存空间

注释

  • 不同汇编的注释方式不同

  • 单行注释: @ ;

  • 多行注释: /* */

混合编程

意义

  • 混合编程就是c语言资源和汇编资源的相互调用

  • 一般工程会有汇编启动程序,启动程序完成堆栈的相关初始化,完毕之后才跳转到c语言的main函数

  • c语言中几乎不可以直接操作寄存器,但是有些特定场景下需要c中操作寄存器,这时候就需要c语言中嵌套汇编的语法

概述

  • 要想实现C和汇编的混合编程必须遵循ATPCS规范

  • 将汇编的标签当作C语言的函数使用

  • 将C语言的函数当作汇编的标签使用

  • 函数参数的传递采用R0-R3进行传递,如果参数的个数大于4个通过压栈的方式进行传递

  • 函数的返回值通过R0返回,如果函数的返回值大于4个字节通过r0-r1返回

  • ATPCS规范中规定ARM采用满减栈

汇编和C语言互相调用

  • 汇编调用C语言的函数

    • 将C语言的函数当作汇编的标签使用

  • c语言调用汇编标签

    • 将汇编的标签当作c语言的函数

  • c语言内联汇编

    • 在某一些特定的场景下需要在c语言中直接使用汇编的语法,此时需要内联汇编。内联汇编的实现需要通过asm关键字进行修饰

    • 格式

;