ARM汇编
ARM架构介绍
内部寄存器
cortex-M3/M4/A7都有r0-r12通用寄存器、r13(SP)栈指针、r14(LR)链接寄存器、r15(PC)寄存器,如图所示:


运行模式
Cortex-A7架构的运行模式有9种,除了User属于非特权模式,其它8种都是特权模式。
| 模式 | 描述 |
|---|---|
| User | 用户模式,非特权模式,大部分程序运行的时候就处于此模式(不能访问系统全部资源) |
| Sys(System) | 系统模式,用于运行特权级的操作系统任务 |
| FIQ | 快速中断模式,进入 FIQ 中断异常 |
| IRQ | 一般中断模式 |
| ABT(Abort) | 数据访问终止模式,用于虚拟存储以及存储保护 |
| SVC(Supervisor) | 超级管理员模式,供操作系统使用 |
| UND(Undef) | 未定义指令终止模式 |
| MON(Monitor) | 用于安全扩展模式 |
| Hyp | 用于虚拟化扩展 |
运行模式可以通过软件、中断或者异常来进行切换。用户模式下需要借助异常来完成模式切换,要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。
所谓“运行模式”,可以这样简单理解:
板子上电时,CPU处于SVC模式,它用的是SVC模式下的寄存器;
程序运行时发生了中断,CPU进入IRQ模式,它用的IRQ模式下的寄存器;
CPU处理完中断,它切换回SVC模式,继续使用SVC模式下的寄存器
CPU发生某种异常时,比如读取内存出错,它会进入ABT模式,使用ABT模式下的寄存器来处理错误。
在某种模式下,CPU执行时使用的是这种模式的资源,比如使用的是这组模式的寄存器。这样就可以免去保存上一个模式所使用的寄存器。
寄存器组
Cortex-A7 有 9 种运行模式,每一种运行模式都有一组与之对应的寄存器组,如下图:

每种模式下有其特定的寄存器,例如在FIQ运行模式下访问R13其实访问SP_fiq寄存器。总的来说可以分为4组:
未备份寄存器,即 R0~R7
在所有模式下R0~R7都是同一物理寄存器,所以从一种模式切换到另外一种模式,里面的数据肯定会被破坏。
备份寄存器,即 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
程序计数器 ,即 R15:总是指向“正在取指”的指令
因为ARM处理器是三级流水线:取指->译码->执行,循环执行。比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中,即 R15(PC)总是指向当前正在执行指令地址再加上 2 条指令的地址,如下图。
处理器处于ARM状态时,每条指令为4个字节, 处理器处于Thumb状态时,每条指令为2字节,所以ARM状态:R15(PC) = 当前执行指令地址 + 4*2字节
Thumb状态:R15(PC) = 当前执行指令地址 + 2*2字节
程序状态寄存器
程序状态寄存器PSR可以分成当前程序状态寄存器CPSR与备份程序状态寄存器SPSR。
所有运行模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问,但是所有运行模式都共用一个 CPSR 必然会导致冲突,因此除了 User 和 Sys 模式以外,其他 7 个模式都配备一个专用的物理状态寄存器,叫做 备份程序状态寄存器(SPSR),当特定异常中断发生时,SPSR用来保存CPSR的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。
由于 SPSR 是 CPSR 的备份,因此 SPSR 和 CPSR 的寄存器结构相同,如下图:

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)模式 |
汇编指令
指令集
ARM指令集,这是32位的,每条指令占据4个字节,高效,但是太占空间
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寄存器的用途:

例如:
/* c 函数 */
int delay(unsigned int d)
{
while (d--);
return 0;
}
在汇编里调用delay函数的方式:
ldr r0, =1000000 /* 给delay函数传参数,保存在r0里 */
bl delay
cmp r0, #0 /* 返回值保存在r0中 */
内敛汇编(C调用汇编)
内敛汇编代码清单1:
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:
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中存取,所以导致返回值错误。

解决方案:在第11行加“&”就可以了,这是告诉编译器,对于%0操作数它是earlyclobber的,不能跟其他操作数共用寄存器
