avatar

Catalog
传统ARM架构寄存器与指令集

前言

五一的时候,想整理一下逆向时遇到的一些指令,又想着顺带对寄存器组织也做一个整理,但在查询资料时发现,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寄存器)。
  • R15:

    • R15又称作程序计数器(PC),所有模式共用一个PC。对于ARM指令集而言,PC总是指向当前指令下面的第二条指令的地址,可以通过向PC赋值,来控制程序跳转,在ARM工作状态下,PC的值为当前指令的地址值加8个字节。
  • CPSR:

    • CPSR(Current Program Status Register)当前程序状态寄存器,与PC一样,在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位,有点类似x86的EFlags寄存器。下图主要介绍其各个位段的含义:

  • 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状态下的R0R7与ARM状态下的R0R7相同。
    • 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个了。下面来看新增的两种运行模式。

运行模式

  • 新增的两个运行模式:

    • 监视模式(MON):用于安全扩展模式,引入TrustZone技术之后,CPU Core被虚拟出secure statenon-secure state,那么就需要一个能够切换两种state的开关,从而增加了一个moniter(MON) mode。如下图所示:

    • 超级监视模式(HYP):超级监视者模式,权限比超级管理员(SVC)略低一些,用于虚拟化扩展

  • 寄存器组织与运行模式的关系(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),对应于条件的成立和不成立。下面来看例子:

Code
1
2
3
4
5
ITETT EQ
MOVEQ R0, #1; //指令1
MOVNE R0, #0; //指令2
MOVEQ R1, #0; //指令3
MOVEQ R2, #0; //指令4

如图,若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, Operand2RnOperand2进行与运算,根据结果修改N和Z的值。

ASR R7, R8, R9

算术右移(Arithmetic Shift Right),两种格式ASR{S}{cond} Rd, Rm, RsASR{S}{cond} Rd, Rm, #sh都很好理解,将Rm算术右移Rs/#sh位后,把值赋给Rd

BKPT #1

BKPT后跟一个立即数(ARM指令为16位,Thumb指令则为8位),断点指令。该指令使处理器进入调试状态。在特定地址执行到该指令时,调试工具可以使用它来调查系统状态。在ARM状态和Thumb状态下,立即数都被 ARM 硬件忽略。但是,调试器可以使用它来存储断点的附加信息。

参考资料

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2021/05/28/%E4%BC%A0%E7%BB%9FARM%E6%9E%B6%E6%9E%84%E5%AF%84%E5%AD%98%E5%99%A8%E4%B8%8E%E6%8C%87%E4%BB%A4%E9%9B%86/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶