前言
五一的时候,想整理一下逆向时遇到的一些指令,又想着顺带对寄存器组织也做一个整理,但在查询资料时发现,ARM寄存器相关的坑越来越多,网上的资料也很杂,没有一个统一的说法,甚至官方的白皮书也有矛盾冲突的地方,再加上弄了好久的项目需要收尾和产出报告,直至今日才有空对这部分内容做整理。当然,本篇的整理也可能会有遗漏与错误,在后期回顾时,会对这些问题作出补充与修改。
ARM-v6架构
关于查阅资料时遇到的第一个问题,就是ARM处理器到底有多少个寄存器。一开始收集的资料,得到的结论都是ARM处理器一共拥有37个寄存器(包括影子寄存器)。但在查阅ARM白皮书以后,发现在9种运行模式下,共有44个寄存器,接着又查阅了另一版本的ARM白皮书,却只有43个寄存器,这些矛盾的说法让人一时难以判断。
接下来,从ARM-v6架构开始,理清思路,弄明白这些问题。
寄存器组织
首先,在网上看到的常说的37个寄存器的寄存器组织模型,其实是ARM-v6及以前架构的,而不是ARM-v7,到了ARM-v7已经不止37个寄存器了。这里先来说经典的ARM-v6架构的寄存器组织,该架构下,共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。具体如下:
- 16个通用寄存器R0~R15
- 5个FIQ模式下的R8~R12的影子寄存器
- 10个异常模式下的R13和R14的影子寄存器
- 1个状态寄存器CPSR
- 5个异常模式下的状态寄存器的影子寄存器SPSR
下面来看这些寄存器的主要用途:
未分组寄存器:
- R0~R7:未被系统用作特殊的用途
- R0~R4:这四个寄存器用于传递子程序调用的第1个到第4个参数,多出的参数通过堆栈来传递;R0寄存器同时用于存放子程序的返回结果。
分组寄存器 :
- R8~R12:
- FIQ模式拥有自己的R8_fiq~R12_fiq
- 其他6种模式下,使用通用寄存器R8~R12
- R13~R14:
- USR和SYS模式共用一组R13~R14
- 其他5种模式各有独自的一组R13~R14,并采用记号来区分不同的物理寄存器,例如R13_fiq,R14_svc等。
- R13:在ARM指令中常用作堆栈指针SP(Stack Pointer)(除USR和SYS模式外,各种模式都有对应的SP_x寄存器)。
- R14:称为子程序链接寄存器LR(Link Register),它有两个功能,一个是在任一模式下用于保存函数的返回地址;另一个是在异常模式下保存异常处理后的返回地址(除USR和SYS模式外,各种模式都有对应的LR_x寄存器)。
- R8~R12:
R15:
- R15又称作程序计数器(PC),所有模式共用一个PC。对于ARM指令集而言,PC总是指向当前指令下面的第二条指令的地址,可以通过向PC赋值,来控制程序跳转,在ARM工作状态下,PC的值为当前指令的地址值加8个字节。
CPSR:
SPSR:
- SPSR(Saved Program Status Register)程序状态保存寄存器,每种异常模式下,都有一个状态寄存器SPSR,用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。用户模式和系统模式不属于异常模式,在这两种模式下访问SPSR,将产生不可预知的后果
以上简要介绍完了基于ARM-v6架构的经典寄存器组织,其中不少寄存器的作用与PC端x86架构下的寄存器类似,下图用于类比这两个架构下功能类似的寄存器,对于有x86基础的玩家能够对ARM寄存器有一个更好的理解。
运行模式
前面在介绍寄存器时,提到不同运行模式下,能够访问的寄存器也不同。这部分就来介绍一下运行模式,ARM-v6架构支持7种运行模式,当前所在的运行模式根据CPSR寄存器的低5位进行判断,这些运行模式可以通过软件改变,也可以通过外部中断或异常处理改变。其中,除用户模式外,其余6种均为特权模式;除用户/系统模式外,其余5种均为异常模式。下面具体来看:
支持的7种运行模式:
- 用户模式(USR):ARM处理器正常的程序执行模式,不能直接切换到其他模式。
- 系统模式(SYS):运行具有特权的操作系统任务,与用户模式类似,但具有可以直接切换到其他模式的特权。
- 快速中断模式(FIQ):用于高速数据传输及通道处理,FIQ异常响应时进入此模式。
- 外部中断模式(IRQ):用于通用的中断处理,IRQ异常响应时进入此模式。
- 管理模式(SVC):又称超级管理员,操作系统使用的保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发)。这个模式的权限级别非常大,一般情况下不能随便使用。
- 数据访问终止模式(ABT):简称退出模式,当数据或指令预取终止时进入该模式,可用于虚拟内存及存储器保护。
- 未定义指令终止模式(UND):支持硬件协处理器的软件仿真,当未定义的指令执行时进入该模式。
运行模式的访问区间:
CSPR[4:0] 模式 用途 可访问寄存器 10000 用户 正常用户执行模式 PC,R0~R14,CPSR 10001 FIQ 处理快速中断 PC,R8_fiq-R14_fiq,R0-R7,CPSR,SPSR_fiq 10010 IRQ 处理普通中断 PC,R13_irq-R14_irq,R0-R12,CPSR,SPSR_irq 10011 SVC 处理软件中断(SWI) PC,R13_svc-R14_svc,R0-R12,CPSR,SPSR_svc 10111 中止 处理存储器故障 PC,R13_abt-R14_abt,R0-R12,CPSR,SPSR_abt 11011 未定义 处理未定义的指令陷阱 PC,R13_und-R14_und,R0-R12,CPSR,SPSR_und 11111 系统 运行特权操作系统任务 PC,R0~R14,CPSR 寄存器组织与运行模式的关系(ARM-v6):
工作状态
除了寄存器组织与运行模式这两个基本概念,ARM处理器有两种工作状态,即ARM状态与Thumb状态,处理器可以在这两种状态之间随意切换。当处理器处于ARM状态时,会执行32位对齐的ARM指令(4字节,因此前面提到,ARM工作状态下,PC指向的地址是当前地址+8字节);当处理器处于Thumb状态时,执行的是16位对齐的Thumb指令。Thumb状态下对寄存器的命名与在ARM状态下有一些差异,它们的关系如下:
2种工作状态的命名差异(ARM/Thumb):
- Thumb状态下的R0-R7与ARM状态下的R0-R7相同。
- Thumb状态下的CPSR与ARM状态下的CPSR相同。
- Thumb状态下的FP对应于ARM状态下的R11。
- Thumb状态下的IP对应于ARM状态下的R12。
- Thumb状态下的SP对应于ARM状态下的R13。
- Thumb状态下的LR对应于ARM状态下的R14。
- Thumb状态下的PC对应于ARM状态下的R15。
Thumb工作状态下寄存器组织与运行模式的关系:
小结
通过上述对ARM-v6架构的介绍,了解到,在早期ARM-v6及以前的架构下,共有37个32位寄存器,7种运行模式以及2种运行状态,这也是大部分人所了解的ARM处理器的架构,也是最经典的一套配置。
ARM-v7架构
网上很多资料说的并不准确,37个寄存器的情况应属于ARM-v6及之前的经典ARM架构,并不是ARM-v7架构,到了ARM-v7架构,引入了TrustZone与虚拟化以后,又新增了两个运行模式,也随之增加了一些影子寄存器,此时寄存器的数量,也就不止37个了。下面来看新增的两种运行模式。
运行模式
新增的两个运行模式:
寄存器组织与运行模式的关系(ARM-v7):
由图,这两张图均是ARM-v7架构的寄存器组织,寄存器的数量分别为43个和44个,这里就对这个矛盾的结论作出解释。经过前文的学习,我们知道ARM-v6及早期架构的ARM处理器包含37个32位的寄存器。在进入ARM-v7的时代以后,随着TrustZone以及虚拟化的引入,又增加了2种运行模式,与之相对的,影子寄存器(Banked Register)也增加了一些。由上图,左图增加了6个影子寄存器,右图增加了7个影子寄存器,区别在于HYP模式下的R14(LR_hyp)寄存器是否存在。
现在来研究这种矛盾出现的情况,先看左图,这张图源自《Arm Cortex-A Series Programmer’s Guide》p45页的插图,也就是官方ARM白皮书里的内容,该白皮书是2011年发布的;再看右图,这张图源自《Arm Cortex-A Series Programmer’s Guide for ARMv8-A》p50页的插图,同样是官方ARM白皮书里的内容,但是要更新一些,是2015年发布的。这里需要补充一个条件,ARM-v8架构是2011年10月官宣的,在此之前,ARM架构处理器,均是32位的运行状态。现在再来看这个LR_hyp寄存器,在32位的运行状态下,它的作用是保存异常返回地址,也就是说LR_hyp和ELR_hyp是公用一个。在64位运行状态下是独立的。因此,在2011年那版的白皮书就给它省略掉了,到了2015年,为了过渡到ARM-v8架构,自然也就独立出来了这个寄存器。这样也就解释清了,为什么官方也会有43个寄存器与44个寄存器这两种说法。当然这些均是ARM-v7架构下的,到了ARM-v8架构,情况又不一样了,这部分留到下篇再讲。
ARM汇编指令(32-bit)
前面对于架构与寄存器组织的介绍,更应该算是本篇的附加部分,起初写这篇博客的目的,就是整理一些遇到的不太熟悉的ARM指令,本篇主要介绍32位ARM汇编指令,64位的则会放在下一篇介绍Arm-v8架构的后半部分。对于一些常见且常规的指令,这里就不详细分析,这部分可以参考ARM汇编语言与azeria实验室-ARM指令集,或在文章参考资料ARM汇编指令部分处找到更多32位基础汇编指令的介绍。本篇以实际逆向时遇到的(32位)指令为主,后期也会不定期更新。
VST1.64 {D8-D9}, {R0}!
将D8, D9中的元素存储到R0所指的地址上。VST/VLD
属于Neon指令,关于Neon指令集的基础内容可参考此篇文章。
VMOV.I32 Q4, #0
VMOV
指令属于Neon指令,用于将立即数插入到一个单精度/双精度的寄存器中。
VPUSH {D8-D9}
VPUSH
指令属于Neon指令,用来将一组扩展寄存器Push到栈顶。
IT
分支语句(If-Then),该指令根据特定条件来执行紧随其后的1~4条指令,格式为IT{x{y{z}}} {cond}
。其中x、y、z分别是执行第二、三、四条指令的条件,可取的值为T(Then)或E(Else),对应于条件的成立和不成立。下面来看例子:
1 | ITETT EQ |
如图,若EQ条件符合(根据CPSR寄存器Z的值判断),执行指令1、3、4的mov操作,否则执行指令2的mov操作。
LDRD.W R1, R2, [R7, #arg0]
看名字以为是加载寄存器对(LoaD Register Dual),实际上是加载到一对寄存器。从基址寄存器(这里是R7)加上偏移后所指向的地址,取两个字(Words),并将这两个字加载到目标寄存器中(这里是R1和R2)。STRD.W
与之作用相反。
CBZ R0, loc_BB8
CBZ属于跳转指令(可以理解为x86汇编中的jcc指令),常见跳转指令可以参考下图:
DCD
DCD伪指令属于数据定义指令,用于分配内存,并初始化内存上的内容,相关指令如下图所示:
BIC R4, R3, #0x1FC0
位清零指令(BIt Clear),格式为BIC{S}{cond} {Rd}, Rn,
,BIC指令用于清楚操作数1的某些位(此处为R3),并把结果放置到目的寄存器中(此处为R4),同时根据操作的结果更新CPSR中相应的条件标志位。操作数2为32位的掩码(此处为0x1FC0)。
MCR P15, 0, R3, C3, C0, 0
从寄存器到协处理器的数据传送指令(Move to Coprocessor from Register),此处P15
为指令所针对的协处理器名称,第一个0
是一个4位的处理器特定的操作码,R3
是要传输的ARM寄存器。C3, C0
是协处理器寄存器,第二个0
一个可选的3位协处理器特定操作码。这条语句的含义是,将ARM处理器寄存器R3中的数据传送到协处理器P15的寄存器C3和C0中。MRC
指令的作用与之相反。
PC
理解该指令需要先了解ARM处理器的三级流水线机制。
由图,一条汇编指令的运行有三个步骤,取指、译码、执行。当第一条指令执行时,第二条指令已经开始译码,第三条指令正在取值。因此,通常来说,PC指向的是当前指令后面第二条指令的地址。来看下面的例子
如图,首先这里的LDR.W
也要说明一下,它是个伪指令,用来从文字池读取常量,且与PC有关的指令。来看PC的作用,首先R10寄存器获取到值off_15108-0xBF14
,接下来ADD指令,令PC与R10相加,由于PC指向当前执行指令后面的第二条指令。因此当执行到ADD R10, PC
时,PC指向的是LDR.W R0, [R10]
这条指令,此时PC的值为0xBF14
,因此加上R10后,得到是值刚好是off_15108
。当然,这里仅限于此类情况,PC的值还会受到各类异常或所执行的指令集的影响。可以参考以下两篇文章作详细了解关于ARM的PC指针,arm pc指令。
STMEA.W SP, {R0, R9, R11}
32位下数据块传输指令(64位下为LDP、STP),格式为LDM/STM {} {类型} 基址寄存器, 寄存器列表
。LDM和STM可以实现在一组寄存器和一块连续的内存单元之间传输数据。LDM为加载多个寄存器,STM为存储多个寄存器。LDM、STM的主要用途是现场保存,数据复制,参数传递,其模式有8种:
- 用于数据块传输:
- IA:每次传送后地址+4
- IB:每次传送前地址+4
- DA:每次传送后地址-4
- DB:每次传送前地址-4
- 用于堆栈操作:
- FA:满递增堆栈
- FD:满递减堆栈
- EA:空递增堆栈
- ED:空递减堆栈
- 可选后缀:
- !:若选用该后缀,表示请求回写,即W=1,则当数据传送完毕之后,将最后的地址写入到基址寄存器中;否则W=0,表示请求不写回,基址寄存器的内容不改变。
篇幅原因,这里仅作概括,更多关于数据块传输指令的完整介绍可以参考此篇
TST R0, #0x3F8
位测试,格式为TST{cond} Rn, Operand2
,将Rn
与Operand2
进行与运算,根据结果修改N和Z的值。
ASR R7, R8, R9
算术右移(Arithmetic Shift Right),两种格式ASR{S}{cond} Rd, Rm, Rs
和ASR{S}{cond} Rd, Rm, #sh
都很好理解,将Rm
算术右移Rs/#sh
位后,把值赋给Rd
。
BKPT #1
BKPT
后跟一个立即数(ARM指令为16位,Thumb指令则为8位),断点指令。该指令使处理器进入调试状态。在特定地址执行到该指令时,调试工具可以使用它来调查系统状态。在ARM状态和Thumb状态下,立即数都被 ARM 硬件忽略。但是,调试器可以使用它来存储断点的附加信息。
参考资料
Armv6/Armv7部分:
- 《Android软件安全权威指南p223~p227》—— 丰生强
- 《Arm Cortex-A Series Programmer’s Guide》2011
- 《Arm Cortex-A Series Programmer’s Guide for ARMv8-A》2015
- 百度百科-影子寄存器
- 维基百科-ARM架构
- CSDN-ARM处理器的7种工作模式和2种工作状态
- CSDN-ARM寄存器 详解
- CSDN-ARM架构寄存器介绍
- CSDN-Cortex-A内核寄存器
- CSDN-ARM体系架构总结
- 博客园-ARM处理器的9种模式详解
- 简书-ARM Trustzone 技术(二) ARMv7-A Processor modes & registers 的安全扩展
- 知乎-ARM汇编语言-简介[一]
- azeria实验室-ARM数据类型与寄存器
- ARM-v8架构寄存器组织
- Cortex-M与Cortex-A处理器运行模式与寄存器组对比
ARM汇编指令部分:
- 百度文库-ARM总汇编指令列表
- StackOverFlow-What VST/VLD actually do?
- NEON指令集
- 博客园-ARM指令集
- CSDN-ARM指令集 –RISC精简指令集
- CSDN-关于ARM的PC指针(什么时候PC+8, PC+4, PC-4, PC-8)
- CSDN-arm pc指令
- CSDN-arm汇编指令之数据块传输(LDM,STM)
- CSDN-ARM中的条件执行指令(IT指令)
- ARM Infocenter-BKPT指令
- ARM Infocenter-DCD/DCDU伪指令
- ARM Infocenter-LDR伪指令
- ARM Infocenter-VMOV指令
- ARM Infocenter-MCR指令
- ARM Infocenter-BIC指令
- ARM Infocenter-IT指令