# ARM汇编 ## ARM架构介绍 ### 内部寄存器 cortex-M3/M4/A7都有r0-r12通用寄存器、r13(SP)栈指针、r14(LR)链接寄存器、r15(PC)寄存器,如图所示: ![](media/image-20210713083838833.png) ![](media/image-20210713083854409.png) ### 运行模式 Cortex-A7架构的运行模式有9种,除了User属于非特权模式,其它8种都是特权模式。 | 模式 | 描述 | | ------------------- | ------------------------------------------------------------ | | User | 用户模式,非特权模式,大部分程序运行的时候就处于此模式(不能访问系统全部资源) | | **Sys(System)** | 系统模式,用于运行特权级的操作系统任务 | | **FIQ** | 快速中断模式,进入 FIQ 中断异常 | | **IRQ** | 一般中断模式 | | **ABT(Abort)** | 数据访问终止模式,用于虚拟存储以及存储保护 | | **SVC(Supervisor)** | 超级管理员模式,供操作系统使用 | | **UND(Undef)** | 未定义指令终止模式 | | **MON(Monitor)** | 用于安全扩展模式 | | **Hyp** | 用于虚拟化扩展 | 运行模式可以通过软件、中断或者异常来进行切换。用户模式下需要借助异常来完成模式切换,要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。 所谓“运行模式”,可以这样简单理解: 1. 板子上电时,CPU处于SVC模式,它用的是SVC模式下的寄存器; 2. 程序运行时发生了中断,CPU进入IRQ模式,它用的IRQ模式下的寄存器; 3. CPU处理完中断,它切换回SVC模式,继续使用SVC模式下的寄存器 4. CPU发生某种异常时,比如读取内存出错,它会进入ABT模式,使用ABT模式下的寄存器来处理错误。 在某种模式下,CPU执行时使用的是这种模式的资源,比如使用的是这组模式的寄存器。这样就可以免去保存上一个模式所使用的寄存器。 ### 寄存器组 Cortex-A7 有 9 种运行模式,每一种运行模式都有一组与之对应的寄存器组,如下图: ![](media/image-20210720111831851.png) 每种模式下有其特定的寄存器,例如在FIQ运行模式下访问R13其实访问SP_fiq寄存器。总的来说可以分为4组: 1. 未备份寄存器,即 R0~R7 在所有模式下R0~R7都是同一物理寄存器,所以从一种模式切换到另外一种模式,里面的数据肯定会被破坏。 2. 备份寄存器,即 R8~R14 - R8~R12 寄存器有两种物理寄存器,FIQ 是快速中断模式,这个中断模式要求快速执行!因为 FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用保存和恢复R8~R12,从而加速中断的执行过程 - 在快速中断模式下(FIQ)它们对应着Rx_irq(x=8~12)物理寄存器, - 其他模式下对应着 Rx(8~12)物理寄存器。 - R13(SP) :栈指针,有8个物理寄存器,User和Sys模式共用,其余七个在不同模式下使用不同的寄存器 - R14(LR)来存放当前子程序的返回地址:如果使用 BL 或者 BLX来调用子函数的话,R14(LR)被设置成该子函数的返回地址,在子函数中,将 R14(LR)中的值赋给 R15(PC)即可完成子函数返回,如`mov pc,lr` 3. 程序计数器 ,即 R15:总是指向“正在取指”的指令 因为ARM处理器是三级流水线:**取指->译码->执行**,循环执行。比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中,即 R15(PC)总是指向当前正在执行指令地址再加上 2 条指令的地址,如下图。 ![](media/image-20210720140259667.png) 处理器处于ARM状态时,每条指令为4个字节, 处理器处于Thumb状态时,每条指令为2字节,所以 1. ARM状态:R15(PC) = 当前执行指令地址 + 4*2字节 2. Thumb状态:R15(PC) = 当前执行指令地址 + 2*2字节 4. 程序状态寄存器 程序状态寄存器PSR可以分成当前程序状态寄存器CPSR与备份程序状态寄存器SPSR。 所有运行模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问,但是所有运行模式都共用一个 CPSR 必然会导致冲突,因此除了 User 和 Sys 模式以外,其他 7 个模式都配备一个专用的物理状态寄存器,叫做 备份程序状态寄存器(SPSR),当特定异常中断发生时,SPSR用来保存CPSR的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。 由于 SPSR 是 CPSR 的备份,因此 SPSR 和 CPSR 的寄存器结构相同,如下图: ![](media/pu0s3qolqhn.png) - **N(bit31)**:当两个补码表示的有符号整数运算的时候, N=1 表示运算结果为负数, N=0表示结果为正数。 - **Z(bit30)**: Z=1 表示运算结果为零, Z=0 表示运算结果不为零,对于 CMP 指令, Z=1 表示进行比较的两个数大小相等。 - **C(bit29)**: - 在加法指令中,当结果产生了进位,则 C=1,表示无符号数运算发生上溢,其它情况下 C=0 - 在减法指令中,当运算中发生借位,则 C=0,表示无符号数运算发生下溢,其它情况下 C=1 - 对于包含移位操作的非加/减法运算指令, C 中包含最后一次溢出的位的数值 - 对于其它非加/减运算指令, C 位的值通常不受影响。 - **V(bit28)**: 对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时, V=1 表示符号位溢出,通常其他位不影响 V 位。 - **Q(bit27)**: 仅 ARM v5TE_J 架构支持,表示饱和状态, Q=1 表示累积饱和, Q=0 表示累积不饱和。 - **IT1:0**: 和 **IT7:2**一起组成 **IT[7:0]**,作为 IF-THEN 指令执行状态。 - **J(bit24)**:控制指令执行状态,表明本指令是ARM指令还是Thumb指令,如表 | J | T | 描述 | | ---- | ---- | ------- | | 0 | 0 | ARM | | 0 | 1 | Thumb | | 1 | 1 | ThumbEE | | 1 | 0 | Jazelle | - **GE[3:0]**(bit19:16): SIMD 指令有效,大于或等于。 - **IT[7:2]**(bit15:10): 参考 IT[1:0]。 - **E(bit9)**: 大小端控制位, E=1 表示大端模式, E=0 表示小端模式。 - **A(bit8)**: 禁止异步中断位, A=1 表示禁止异步中断。 - **I(bit7)**: I=1 禁止 IRQ, I=0 使能 IRQ。 - **F(bit6)**: F=1 禁止 FIQ, F=0 使能 FIQ。 - **T(bit5)**: 控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一起表明指令类型,参考 J(bit24)位。 - **M[4:0]**: 处理器模式控制位,如下表 | M[4:0] | 运行模式 | | ------ | ------------------- | | 10000 | User 模式 | | 10001 | FIQ 模式 | | 10010 | IRQ 模式 | | 10011 | Supervisor(SVC)模式 | | 10110 | Monitor(MON)模式 | | 10111 | Abort(ABT)模式 | | 11010 | Hyp(HYP)模式 | | 11011 | Undef(UND)模式 | | 11111 | System(SYS)模式 | ## 汇编指令 ### 指令集 1. ARM指令集,这是32位的,每条指令占据4个字节,高效,但是太占空间 2. Thumb指令集,这是16位的,每条指令占据2字节,节省空间要节省空间时用Thumb指令,要效率时用ARM指令 一个CPU既可以运行Thumb指令,也能运行ARM指令。程序状态寄存器中有一位**T(bit5)**,它等于1时表示当前运行的是Thumb指令。 问题:假设函数A是使用Thumb指令写的,函数B是使用ARM指令写的,怎么让CPU在执行A函数是进入Thumb状态,在执行B函数时进入ARM状态? 答:调用函数A时,让PC寄存器的BIT0等于1,即:PC=函数A地址+(1<<0);调用函数B时,让PC寄存器的BIT0等于0:,即:PC=函数B地址,是不是如此麻烦。 所以引入Thumb2指令集,它支持16位指令、32位指令混合编程,这样自动实现切换。总结下就出现下面的三种指令集: - CODE32:表示使用ARM指令集 - CODE16:表示使用Thumb指令集 - THUMB:表示使用Thumb2指令集 ### 汇编代码分析 代写…… ## 汇编调用C函数 在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。约定r0-r15寄存器的用途: ![](media/image-20210723084017968.png) 例如: ```c /* c 函数 */ int delay(unsigned int d) { while (d--); return 0; } ``` 在汇编里调用`delay`函数的方式: ``` ldr r0, =1000000 /* 给delay函数传参数,保存在r0里 */ bl delay cmp r0, #0 /* 返回值保存在r0中 */ ``` ## 内敛汇编(C调用汇编) > **内敛汇编代码清单1:** > > ```c > int add(int a, int b) > { > int sum; > __asm__ volatile ( > "add %0, %1, %2" // 汇编代码:实现sum=a+b > :"=r"(sum) // 结果,存放在sum中,汇编代码中可以用%0引用 > :"r"(a), "r"(b) // 把a和b放入某些寄存器,汇编代码可以使用%1 %2引用 > :"cc" > ); > return sum; > } > ``` > **内敛汇编代码清单2**: > > ```c > int add(int a, int b) > { > int sum; > __asm__ volatile ( > "add %[result], %[val1], %[val2]" // 汇编代码:实现sum=a+b > :[result]"=r"(sum) // 结果,存放在sum中,汇编代码中用%[result]引用 > :[val1]"r"(a), [val2]"r"(b) // 把a和b放入寄存器,汇编代码使用%[val1] %[val2]引用 > :"cc" > ); > return sum; > } > ``` ### 内敛汇编语法 ``` asm [ asm-qualifiers ] ( assembler template /* 汇编代码,每行用双引号"",换行用\n */ [ : output operands ] /* 输出操作 */ [ : input operands ] /* 输入操作 */ [ : list of clobbered registers ] /* 影响的寄存器列表 */ ); ``` - `asm`:也可以写作`__asm__`,表示这是一段内联汇编; - `asm-qualifiers`:有三个取值 - volatile:告诉编译器不要随便优化这段代码,例如使用`mov r0, r0`用来做短暂延时。 - inline:不讲 - goto:不讲 - `assembler template`:具体汇编指令代码,双引号,`\n`分开; - `output operands`:输出操作数,格式如下: ``` [ [asmSymbolicName] ] constraint (cvariablename) ``` - `asmSymbolicName`:符号名,随便,也可以不写; - `constraint`:表示约束; | constraint | 描述 | | ---------- | ------------------------------------------------------------ | | m | memory operand,表示要传入有效的地址,只要CPU能支持该地址,就可以传入 | | r | register operand,寄存器操作数,使用寄存器来保存这些操作数 | | i | immediate integer operand,表示可以传入一个立即数 | `constraint`前还可以加上一些修饰字符,比如“=r”、“+r”、“=&r”,含义如下: | constraint Modifier Characters | 描述 | | ------------------------------ | -------------------------------------------- | | = | 表示内联汇编会修改这个操作数,即:写 | | + | 这个操作数即被读,也被写 | | & | 它是一个earlyclobber操作数 ,**后面重点讲** | - `cvariablename`:C语言的变量名: **示例1**:汇编代码中会通过某个寄存器把结果写入c语言的sum变量。在汇编代码中可以使用`“%[result]”`来引用它 ``` [result] "=r" (sum) ``` **示例2**:变量a、b的值会放入某些寄存器。在汇编代码中可以使用%1、%2等使用它们。 ``` "r"(a), "r"(b) ``` - `InputOperands`:输入操作符,接收输入参数。 格式如下: ``` [ [asmSymbolicName] ] constraint (cexpression) ``` - `asmSymbolicName`:符号名,随便取,也可以不写; - `constraint`:表示约束,参考上一小节,跟OutputOperands类似; - `cexpression`:C语言的表达式。 **示例1:**它的意思变量a、b的值会放入某些寄存器。在汇编代码中可以使用%[a_val]、%[b_val]使用它们 ``` [a_val]"r"(a), [b_val]"r"(b) ``` **示例2:**变量a、b的值会放入某些寄存器。在汇编代码中可以使用%0、%1等使用它们 ``` "r"(a), "r"(b) ``` - `Clobbers`:在汇编代码中,对于`OutputOperands`所涉及的寄存器、内存,肯定是做了修改。但是汇编代码中,也许要修改的寄存器、内存会更多。比如在计算过程中可能要用到r3保存临时结果,我们必须在`Clobbers`中声明r3会被修改。如下示例所示: ``` : "r0", "r1", "r2", "r3", "r4", "r5", "memory" ``` 常用Clobbers如下所示: | Clobbers | 描述 | | -------- | ------------------------------------------------------------ | | cc | 表示汇编代码会修改`flags register` | | memory | 表示汇编代码中,除了`InputOperands`和`OutputOperands`中指定的之外, 还会会读、写更多的内存 | ### earlyclobber 首先看下面的实例: 输出操作数%0对应的寄存器是r3,输入操作数%1对应的寄存器也是r3。第8行更新了%0的值后,第9行又修改%1的值,由于%0、%1是同一个寄存器r3,所以%0的值也被修改了。而我们的最终返回的累加值在%0中存取,所以导致返回值错误。 ![](media/image-20210207102931347.png) 解决方案:在第11行加“&”就可以了,这是告诉编译器,对于%0操作数它是earlyclobber的,不能跟其他操作数共用寄存器 ![](media/image-20210207103226044.png)