ARM汇编指令详解
ARM汇编指令详解
ARM的编程模式和七种模式
基本设定
- 架构(32位)
- 约定
- Byte(字节):8bits
- Halfword(半字) :16 bits (2 byte)
- Word(字):32bits(4 byte)
- 约定
- 指令集
- ARM指令集(32位)
- Thumb指令集(16位)
- Thunmb指令集 (16位&32位)
工作模式
- 种类:七种
- 非特权模式(Normal:普通模式)
- User (用户模式):非特权模式,大部分时候在这个模式下工作
- 特权模式(privilege:特权模式)
- FIQ(快速中断模式):当一个高优先级(fast)中断产生时将会进入这种模式
- IRQ(普通中断模式):当一个低优先级(normal)中断产生时会进入这种模式
- SVC(管理模式):当复位或软中断指令执行时进入
- Abt(数据访问终止模式):当存取异常时进入
- und(未定义指令终止模式):当执行未定义指令时进入
- sys(系统模式):使用和User模式相同的寄存器的特权模式
Privilege除了System模式外,其他几种为异常模式
各种模式的切换,程序员通过代码切换(CPSR寄存器);也可以通过CPU在某些情况下自动切换(中断或者按复位键)
为什么要有这么多模式?
操作系统有安全级别要求,多模式为了方便操作系统多种角色安全等级需求
ARM寄存器组织
- ARM处理器有37个32位长的寄存器
- 一个用作PC(程序指针)
- 一个用作CPSR(程序状态寄存器)
- 5个用于SPSR(备份程序状态寄存器)
- 30个用作通用寄存器
其中r0~r3主要用于子程序间传递参数,r4 ~ r11 主要用于保存局部变量,但在Thumb程序中,通常只能使用r4 ~ r7来保存局部变量;r12 用作子程序间scratch 寄存器,即 ip 寄存器; r13 通常用做栈指针,即 sp; r14 寄存器又被称为连接寄存器(lr),用于保存子程序以及中断的返回地址; r15 用作程序计数器(pc),由于 ARM 采用了流水线机制,当正确读取了 PC 的值后,该值为当前指令地址加 8 个字节,即 PC 指向当前指令的下两条指令地址。
CPSR和SPSR都是程序状态寄存器,其中SPSR是用来保存中断前的CPSR中的值,以便在中断返回之后恢复处理器程序状态。
2.CPSR寄存器详解
注意: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内核
- 拷贝CPSR到SPSR
- 设置适当的CPSR位
- 改变处理器状态进入ARM态
- 改变处理器模式进入相应的异常模式
- 设置中断禁止禁止相应中断(如果需要)
- 保存返回地址到LR(R14)
- 设置PC为相应的异常向量
从异常返回时
- 从SPSR恢复CPSR
- 从LR恢复PC
注意:这些操作只能在ARM态执行
异常处理中有些是硬件自动做的,有些是程序员需要自己做的。需要搞清楚哪些是需要自己做的,才知道如何写代码。
以上说的这些CPU设计时提供的异常向量表,一般称为一级向量表。有些CPU为了支持多个中断,还会提供二级中断向量表,处理思路类似这里说的一级中断向量表。
ARM汇编指令集
- 指令与伪指令(汇编)
- 指令:指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。
- 伪指令: 伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后的伪指令最终不会生成机器码。
- 两种风格
- ARM官方的指令风格:指令一般用大写,一般用于Windows的开发环境(ADS,MDK等)如:LDR R0,[R1]
- GNU风格:指令一般用小写字母,linux中常用。如:ldr r0,[r1]
ARM汇编特点
LDR/STR架构
- ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中的内容加载入CPU中通用寄存器中才能被CPU处理。
- ldr(load register)指令将内存内容加载入通用寄存器。
- str(store register)指令将寄存器内容存入内存空间中。
- ldr/str 组合用来实现ARM CPU和内存数据交换。
八种寻址方式
- 寄存器寻址 mov r1 ,r2 r2 的值赋值给r1
- 立即寻址 mov r0 ,#0xFF00 #后面的数值直接赋值给r0
- 寄存器移位寻址 mov r0 ,r1 ,lsl #3 r1中的数值左移三位,然后赋值给r0(就是乘八)
- 寄存器间接寻址 ldr r1 ,[r2] 类似于指针,r2 中存操作数的地址,[]类似于解引用
- 基址变址寻址 ldmia r1 ,[r2,#4] r2为基地址,r2里面的地址加上4,这个地址存的值读到寄存器中
- 多寄存器寻址 ldmia r1,{r2 - r7,r12} r1 中的八个地址读到r2到r7 和r12中 (类似于数值中的八个元素)
- 堆栈寻址 stmfd sp!,{r2 - r7 ,lr} 将寄存器列表中的寄存器(r2到r7,lr) 存入堆栈
- 相对寻址 beq flag
指令后缀
同一指令经常附带不同后缀,变成不同的指令,经常使用的后缀有:
B(byte)功能不变,操作长度变成八位。
H(half word)功能不变,长度变成十六位。
! 如果指令地址表达式不含 “!” 后缀,则基址寄存器中的地址不会发生变化,指令中含有则变化,变化结果如下:基址寄存器中的值(执行后) = 指令执行前的值 + 地址偏移量
注意:
“!” 后缀必须紧跟在地址表达式后面,而地址表达式要有明确的地址偏移量。
“!”后缀不能用于R15 (PC)的后面
当用于单个寄存器后面时,必须确定这个寄存器有隐形的偏移量,eg: “STMDB SP!,{R3,R5,R7}”此时地址基址寄存器SP的隐形偏移量是4。
S(signed)功能不变,操作数变为有符号,如 ldr ldrh ldrsb ldrsh。
S(S标志)功能不变,影响CPSR标志位,如mov 和movs r0,#0。指令中使用“S”后缀,指令执行后状态寄存器的条件标志位将被刷新;不使用”S”后缀是,指令执行后状态寄存器的条件标志位不会发生变化。此标志经常用于对条件进行测试,例如:是否溢出,是否进位等;根据这些变化,就可以进行一些判断,是否大于,是否相等,从而可能影响指令执行顺序。
两个S用于不同的指令,名称相同,但是和不同的指令结合却有不同的作用。
条件执行后缀
操作码 条件码助记符 标志 含义 0000 EQ Z = 1 相等 0001 NE Z = 0 不相等 0010 CS/HS C = 1 无符号数大于或等于 0011 CC/LO C = 0 无符号数小于 0100 MI N = 1 负数 0101 PL N = 0 正数或零 0110 VS V = 1 溢出 0111 VC V = 0 没有溢出 1000 HI C=1,Z=0 无符号数大于 1001 LS C=0,Z=1 无符号数小于或等于 1010 GE N=V 有符号数大于或等于 1011 LT N!=V 有符号数小于 1100 GT Z=0,N=V 有符号数大于 1101 LE Z=1,N!=V 有符号数小于或等于 1110 AL 任意 无条件执行(指令默认条件) 1111 NV 任意 从不执行(不要使用) 条件执行后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果。
条件执行后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。
多指令流水线
常用ARM指令
数据处理指令
- 数据传输指令 mov mvn
- 算术指令 add sub rsb adc sbc rsc
- 逻辑指令 and orr eor bic
- 比较指令 cmp cmn tst teq
- 乘法指令 mvl mla umull umlal smull smlal
- 前导零计数 clz
CPSR访问指令
- mrs & msr
- mrs 用来读psr,msr用来写psr
- CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。
- mrs & msr
跳转(分支)指令
- b & bl & bx
- b 直接跳转 (就没打算返回)
- bl (branch and link ),跳转前把返回地址放入lr中,以便于返回,以便于函数调用
- bx 带状态切换的跳转。最低位为1时,切换到Thumb指令执行,为0时,解释为ARM指令执行。一般用于异常处理的跳转。
- b & bl & bx
访问指令
- ldr/str & ldm/stm & swp
- 单个字/半字/字节 访问 ldr/str
- 多字节批量访问 ldm/stm
- swp r1,r2 ,[r0] 将r0所指向的存储器中的字数据传输到r1,同时将r2中的字数据传输到r0所指向的内存单元
- swp r1,r1,[r0] 该指令完成将r0所执行的存储器中的子数据与r1中的字数据互换。
- ldr/str & ldm/stm & swp
ARM汇编中的立即数
ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数,因此立即数有合法和非法之分。
合法立即数:并不是所有的32bit立即数都是可以使用的合法立即数。只有那些通过将一个8bit立即数循环右移偶数位可以得到的立即数才可以在指令中使用。
注意:加载立即数一般采用伪指令 ldr, 编译器会自动处理非法立即数,这里了解即可
软中断指令
swi (software interrupt)
软中断指令用来实现操作系统中系统调用
协处理器(CP15)操作指令
CP15,即通常所说的系统控制协处理器(System Control Coprocesssor),Soc内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
ARM处理器支持16个协处理器。在程序执行过程中,每个协处理器忽略属于ARM处理器和其他协处理器的指令。当一个协处理器硬件不能执行属于它的协处理器指令时,将产生一个未定义指令异常中断,该异常中断处理程序中,可以通过软件模拟该硬件操作。
- mcr & mrc
- mrc用于读取CP15中的寄存器
- mcr用于写入CP15中的寄存器
- 使用方法
- mcr{} p15,
, , , , { } - opcode_1:对于cp15永远为0
- Rd:ARM的普通寄存器
- Crn:cp15的寄存器,合法值是c0~c15
- Crm:cp15的寄存器,一般均设为c0
- opcode_2:一般省略或为0
- mcr{} p15,
- 举例
- mrc p15, 0, r0, c1, c0, 0 该指令将协处理器 p15 的寄存器中的数据传送到ARM处理器的寄存器中
- mcr p15, 0, r0, c1, c0, 0 该指令将ARM处理器寄存器 r0 中的数据传送到协处理器 p15 的寄存器 c1 和 c0 中。
协处理器的学习要点
不必深究
只看一般用法,不详细区分参数细节,否则会陷入很多复杂未知中。关键在于理解,而不在于记住。
- mcr & mrc
批量数据加载存储指令(LDM/STM与栈的处理)
为什么需要多寄存器访问指令
ldr/str 每周期只能访问4字节内存,如果需要批量读取,写入内存时太慢,解决方案是stm/ldm
stm/ldm
- ldm(load register mutiple) 加载多个寄存器
- stm (store register mutiple)存储多个寄存器
举例:
stmia sp,{r0 - r12}
将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放(0x3001030),指令完成。
一个访问周期同时完成13个寄存器的读写
八种后缀
- ia (increase after)先传输,再地址+4
- ib (increase before) 先地址+4,再传输
- da (decrease after)先传输,再地址-4
- db(decrease before)先地址-4,再传输
- fd(full decrease)满递减堆栈
- ed(empty decrease)空递减堆栈
- fa(full after ) 满递增堆栈
- ea(empty afer)空递增堆栈
四种栈
- 空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出
- 满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针
- 增栈:栈指针移动时向地址增加的方向移动的栈
- 减栈:栈指针移动时向地址减小的方向移动的栈
后缀符号的作用
!
的作用
ldmia r0 ,{r2 - r3}
ldmia r0! , {r2 - r3}
感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。
^
的作用ldmfd sp!, {r0 - r6, pc}
ldmfd sp!, {r0 - r6, pc}^^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
总结:批量读取或写入内存时要用ldm/stm指令各种后缀以理解为主,不需记忆,最常见的是stmia(空堆栈递增)和stmfd(满堆栈递减)。
谨记:操作栈时使用相同的后缀(LDM/STM)就不会出错,不管是满栈还是空栈、增栈还是减栈。
ARM汇编伪指令
伪指令的意义
- 伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。
- 伪指令的意义在于指导编译过程。
- 伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。
GUN汇编中的一些符号
@
用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//
类似。#
做注释,一般放在行首,表示这一行都是注释而不是代码。:
以冒号结尾的是标号。.
点号在gnu汇编中表示当前指令的地址。#
立即数前面要加#
或$
,表示这是个立即数。
常用GUN伪指令
- .global _start @ 给start外部链接属性
- .section .text @ 指定当前段为代码段
- .ascii .byte .short .long .word
- .quad .float .string @ 定义数据
- .align 4 @ 以16字节对齐
- .balignl 16 0xabcdefgh @ 16字节对齐填充
- .equ @ 类似于C中宏定义
偶尔用到的GUN伪指令
- .end @标识文件结束
- .include @ 头文件包含
- .arm / .code32 @声明以下为arm指令
- .thumb / .code16 @声明以下为thubm指令
最重要的几个伪指令
- ldr 大范围的地址加载指令
- adr 小范围的地址加载指令
- adrl 中等范围的地址加载指令
- nop 空操作
adr与ldr
- adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;
- adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址
- 在哪里
- ldr加载的地址和链接时给定的地址有关,由链接脚本决定。
ARM中有一个ldr指令,还有一个ldr伪指令
一般都使用ldr伪指令而不用ldr指令