欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第七章 ARM 反汇编基础(五)(ARM 汇编指令集)

程序员文章站 2022-06-09 08:43:09
...

ARM 汇编指令集

  • Android 平台的 ARM 汇编指令集根据架构支持类型的不同可分为四大类:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 注意:
    • ARM 指令集一直在变化,armeabi 支持 ARMv7 以下版本的指令集,armeabi-v7a 支持 ARMv7 系列指令集,AArch32 支持的 ARM 指令集兼容之前的版本
    • 编译器支持方面:Clang 只能通过 -target 参数指定 EABI 版本,不能指定具体的 arch 参数;而 gcc 支持用 -arch 参数指定具体的指令集,包括 ARMv2、ARMv2A、ARMv3、ARMv3M、ARMv4、ARMv4T、ARMv5、ARMv5E、ARMv5T、ARMv5TE、ARMv6、ARMv6-M、ARMv6J、ARMv6K、ARMv6T2、ARMv6Z、ARMv6ZK、ARMv7、ARMv7-A、ARMv7-M、ARMv7-R、ARMv7e-M、ARMv7VE、ARMv8-A、ARMv8-A+crc、iWMMXT、iWMMXT2、Native。在 Android 系统不同的 EABI 设置下,ARMv5TE、ARMv7-A、ARMv8-A 是编译器默认的选择

ARM 指令集分类

  • armeabi-v8a 的 AArch32 与 armeabi-v7a 支持所有的 ARM 指令集,按指令的位域和功能可分为这几种:
    • Data-processing and miscellaneous instructions:数据处理与杂项指令
    • Load/store word and unsigned byte:加载存储指令
    • Media instructions:媒体指令
    • Branch, branch with link, and block data transfer:分支、带链接的分支与块数据传输指令
    • Supervisor Call and coprocessor instructions:软中断与协处理器指令
    • unconditionally executed instructions:无条件执行指令

ARM 指令编码

  • ARM 指令集采用 32 位的等长编码格式,按类型的位域分布:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 采用位域方式对一条 ARM 指令进行划分,最基本的域有 cond、op1、op。这三个域的取值会直接影响指令的具体类别与格式。cond 域占用 28 位到 31 位,共四位,记为 bits[31:28],每个位记录了一个条件标志(Conditon Flag)。这四个标志位具体如下:
    • N:bit[31],负数标志
    • Z:bit[30],0 标志
    • C:bit[29],进位标志
    • V:bit[28],溢出标志
  • 每条 ARM 指令的 bits[31:28] 中都有条件标志,若相应的标志位为 1,表示这条指令会影响标志位的结果。指令执行后,可通过查看 APSR 寄存器的 cond 域来查看标志。在应用级别,APSR 对应 CPSR,代表了程序状态寄存器;在系统级别,APSR 对应 SPSR。在 Android 原生 ARM 程序中,我们通常只能访问 CPSR
  • 不同的标志位组合在一起,可表示不同的含义:
cond 助记符 含义(整型) 含义(浮点型) 条件标志
0000 EQ 相等 相等 Z == 1
0001 NE 不等 不等或无序 Z == 0
0010 CS 进位 大于等于或无序 C == 1
0011 CC 进位清除 小于 C == 0
0100 MI 减、负数 小于 N == 1
0101 PL 加、正数或 0 大于等于或无序 N == 0
0110 VS 溢出 无序 V == 1
0111 VC 未溢出 有序 V == 0
1000 HI 无符号大于 大于或无序 C == 1 and Z == 0
1001 LS 无符号小于或等于 小于或等于 C == 0 or Z == 1
1010 GE 有符号大于或等于 大于或等于 N == V
1011 LT 有符号小于 小于或无序 N != V
1100 GT 有符号大于 大于 Z == 0 and N ==V
1101 LE 有符号大于或等于 小于等于或无序 Z == 1 or N != V
1110 无条件 无条件 任何
  • 在浮点数的无符号表示中,至少有一个非数字操作数
  • ARM 指令中的溢出和进位有如下特点:
    • 对无符号数来说,不存在溢出,它的进位即相当于有符号数中的溢出
    • 对有符号数来说,不存在进位,其溢出有如下特点:
      • 两个正数相加,或一个正数减一个负数,结果为负数,表示溢出了
      • 两个负数相加,结果为正数,表示溢出了
      • 一个正数和一个负数相加,不可能溢出

  • op1 域位于 bits[27:25],占三位;op 域位于 bit[4],占一位。它们的取值组合在一起,决定指令所属的分类(Instruction Class)。在 cond 域不全为 1 时,它们的二进制域的值表示如下(下面表述中的值均为二进制值):
    • op1 为 000 与 001:这条指令是数据处理或杂项指令
    • op1 为 010,或 op1 为 011 且 op 为 0:这条指令是加载存储指令
    • op1 为 011 且 op 为 1:这条指令是媒体指令
    • op1 为 100 与 101:这条指令是分支、带链接的分支与块数据传输指令
    • op1 为 110 与 111:这条指令是软中断与协处理器指令
  • 在 cond 域全为 1 时,这条指令是无条件执行指令
  • op1 不同,对应的系统指令也不同。以 op1 为 100 为例,属于分支、带链接的分支与块数据传输指令系列,对应的指令格式:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 可看出,指令的位域分配有了变化,bits[25:20] 的 op 域、bits[19:16] 的 Rn 域及 bit[15] 的 R 域的不同组合,可表示不同的指令。若 op 域为 001001,表示的是 LDM(LDMIA、LDMFD)指令。跟进 LDM 指令的格式:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 可看到,指令本质上是一系列二进制位的数据。但在编写汇编程序时,不可能直接用二进制值,ARM 汇编引入了指令助记符的概念,便于阅读和使用 ARM 中的汇编指令
  • 为详细描述指令不同位域取不同的值可能会对指令造成的影响,ARM 指令参考手册用伪代码说明的形式给出了指令的格式,其中 LDM 指令的伪代码说明如下:
if W == '1' && Rn == '1101' && BitCount(register_list) > 1
    then SEE POP (A32);
n = UInt(Rn);
registers = register_list;
wback = (W == '1');
if n == 15 || BitCount(registers) < 1
    then UNPREDICTABLE;
if wback && registers<n> == '1'
    then UNPREDICTABLE;
  • 上述伪代码的意思:若 W 为 1、Rn 为 1101 且 register_list 中只有 1 位,就查看 POP 指令的格式(说明在这种情况下指令的作用与 POP 指令相同);设置 registers 的值,判断 Rn 的值是否超过 15,以及 registers 中寄存器的相应位是否被设置,从而判断指令的行为是不是 UNPREDICTABLE(不可预测)的
  • LDM 指令在助记符描述中引用了 registers。完整的 LDM 指令格式的描述:
    LDM<c> <Rn>{!}, <registers>

ARM 指令格式解析

  • 以十六进制值 0xE1A01002 为例,解析其对应的 ARM 汇编指令
  • 打开计算器,查看其对应的二进制位信息:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 首先是 cond 域 bits[31:28],其取值不是 0b1111,表示这不是一条无条件执行指令
  • 接着是 op1 域 bits[27:25],其取值为 0b000,表示这是一条数据处理与杂项指令。数据处理与杂项指令的格式:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • bit[25] 的 op 域为 0,bits[24:20] 的 op1 域为 0b11010,bits[7:4] 的 op2 域为 0。查阅 ARM 指令参考手册可知,这是一条数据处理(寄存器形式)指令:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • bits[24:20] 的 op 域为上图(数据处理与杂项指令格式图)中的 op1 域,值为 0b11010,bit[6:5] 的 op2 域的值为 0,bit[11:7] 的 imm5 域的值为 0。查阅 ARM 指令参考手册可知,其所对应的是 MOV(寄存器形式)指令。MOV(寄存器形式)指令格式:
    MOV{S}<c> <Rd>, <Rm>
  • 完整的指令格式的域分布:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • bit[20] 的 S 域是符号域:若值为 1,表示这是一条 MOVS 指令;若值为 0,表示这是一条 MOV 指令。bit[15:12] 的 Rd 域表示目标寄存器,其值为 0b0001(表示 R1 寄存器)。bit[3:0] 的 Rm 域为源寄存器,其值为 0b0010(表示 R2 寄存器)
  • 综上分析,可知这条指令为 MOV R1, R2。可用反汇编器验证,如 Radare2 的 rasm2 命令
  • Radare2 是一款强大的跨平台二进制分析工具,可在 Windows、macOS、Ubuntu 等平台使用
  • 下载地址:Radare2
  • 或在 Ubuntu 执行如下命令快速安装:sudo apt-get install radare2
  • 执行如下命令,查看 MOV R1, R2 指令的十六进制值:
    第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
  • 输出的 0x0210a0e1 与分析的 0xE1A01002 相反,因为分析的指令是小端序的,而用 rasm2 命令输出的结果是大端序的

常见 ARM 指令

数据处理与杂项指令

  • 包括数据传送指令、算术运算指令、逻辑运算指令、比较指令等
  • 主要用于对寄存器间的数据进行操作
  • 所有的数据处理指令均可用 S 后缀设置是否影响状态标志。比较指令不需要 S 后缀,它们会直接影响状态标志
  • 常用的数据处理与杂项指令:
    • MOV

      • MOV 是 ARM 指令集中使用最频繁的指令
      • 功能:将 8 位的立即数或寄存器的内容传送到目标寄存器
      • 格式:MOV{S}<c> <Rd>, <Rm>
      • 示例:
      MOV R0, #8        @R0=8
      MOV R1, R0        @R1=R0
      MOV R2, R1, LSL #2        @R2=R1*4,影响状态标志
      
    • MVN

      • 数据非传送指令
      • 功能:将 8 位的立即数或寄存器的内容按位取反后传送到目标寄存器
      • 格式:MVN{S}<c> <Rd>, #<const>
      • 示例:
      MVN R0, #0xFF        @R0=0xFFFFFF00
      MVN R1, R2        @将 R2 寄存器的数据取反后存入 R1
      
    • ADD

      • 加法指令
      • 功能:将 Rn 寄存器的值与 operand2 的值相加,将结果保存到 Rd 寄存器
      • 格式:ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
      • 示例:
      ADD R0, R1, #2        @R0=R1+2
      ADDS R0, R1, R2        @R0=R1+R2,影响标志位
      ADD R0, R1, LSL #3        @R0=R1*8
      
    • ADC

      • 带进位的加法指令
      • 功能:将 Rn 寄存器的值与 Rm 寄存器的值相加,再加上 CPSR 寄存器的 C 条件标志位的值,最后将结果保存到 Rd 寄存器
      • 格式:ADC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
      • 示例:
      ADD R0, R0, R2
      ADC R1, R1, R3        @这两条指令完成了 64 位加法运算,(R1, R0) = (R1, R0) + (R3, R2)
      
    • SUB

      • 减法指令
      • 功能:用 Rn 寄存器的值减 Rm 寄存器的值,将结果保存到 Rd 寄存器
      • 格式:SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift}
      • 示例:
      SUB R0, R1, #4        @R0=R1-4
      SUBS R0, R1, R2        @R0=R1-R2,影响状态标志
      
    • MUL

      • 32 位乘法指令
      • 功能:将 Rm 寄存器的值与 Rn 寄存器的值相乘,将结果的低 32 位保存到 Rd 寄存器
      • 格式:MUL{S}<c> <Rd>, <Rn>, <Rm>
      • 示例:
      MUL R0, R1, R2        @R0=R1*R2
      MULS R0, R2, R3        @R0=R2*R3,影响 CPSR 的 N 位于 Z 位
      
    • SDIV

      • 有符号数除法指令
      • 格式:SDIV<c> <Rd>, <Rn>, <Rm>
      • 示例:
      SDIV R0, R1, R2
      
    • UDIV

      • 无符号数除法指令
      • 格式:UDIV<c> <Rd>, <Rn>, <Rm>
      • 示例:
      UDIV R0, R1, R2        @R0=R1/R2
      
    • ASR

      • 算术右移指令
      • 功能:将 Rm 寄存器的值算术右移 operand2 位,并用符号位填充空位,将结果保存到 Rd 寄存器
      • 格式:ASR{S}<c> <Rd>, <Rm>, #<imm>
      • 示例:
      ASR R0, R1, #2        @将 R1 寄存器的值作为有符号数右移 2 位后赋给 R0 寄存器
      
    • AND

      • 逻辑与指令
      • 格式:AND{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
      • 示例:
      AND R0, R0, #1        @用来测试 R0 的最低位
      
    • ORR

      • 逻辑或指令
      • 格式:ORR{S}<c> <Rd>, <Rn>, #<const>
      • 示例:
      ORR R0, R0, #0x0F        @指令执行后,保留 R0 的高四位,其余位置为 1
      
    • EOR

      • 异或指令
      • 格式:EOR<c> <Rdn>, <Rm>
      • 示例:
      EOR R0, R0, R0        @执行后,R0=0
      

加载存储指令

  • 加载存储指令完成的工作包括:从寄存器中加载数据与将数据存储到存储器中
  • 常见的加载存储指令:
    • LDR

      • 功能:将数据从存储器加载到寄存器
      • 格式:
        LDR<c> <Rt>, [<Rn>, <Rm>]
        LDR<c> <Rt>, [<Rn>], #+/-<imm12>
        LDR<c> <Rt>, [<Rn>{, #+/-<imm12>}]|
        LDR<c> <Rt>, [<Rn>, #+/-<imm12>]!
        LDR<c> <Rt>, label
        LDR<c> <Rt>, [PC, #-0]
      • 示例:
        • 直接偏移量,示例:
          LDR R8, [R9, #04]
        • 寄存器偏移量,示例:
          LDR R8, [R9, R10, #04]
        • 相对 PC 寄存器,示例:
          LDR R8, label1
    • STR

      • 功能:将数据存储到指定地址的存储单元
      • 格式:
        STR<c> <Rt>, [<Rn>{, #+/-<imm12>}]
        STR<c> <Rt>, [<Rn>], #+/-<imm12>
        STR<c> <Rt>, [<Rn>, #+/-<imm12>]!
        STR<c> <Rt>, [<Rn>, #+/-<Rm>{, <shift>}]{!}
        STR<c> <Rt>, [<Rn>], #+/-<Rm>{, <shift>}
      • 示例:
        STR R0, [R2, #04] @将 R0 寄存器中的数据存储到 R2+4 所指向的存储单元

其他指令

  • 媒体指令使用较少
  • 分支指令也称“跳转指令”,会根据条件标志的不同取值执行不同的分支
  • 常见的分支指令:
    • 跳转指令 B

      • 格式:B<c> <label>
      • B 指令是最简单的分支指令。执行 B 指令时,若条件 c 满足,ARM 处理器将立即跳转到 label 指定的地址处执行
    • 带链接的跳转指令 BL

      • 格式:BL<c> <label>
      • 执行 BL 指令时,若条件 c 满足,首先将当前指令的下一条指令的地址复制到 R14(即 LR)寄存器,然后跳转到 label 指定的地址处继续执行。此指令通常用于调用子程序。在子程序尾部,可通过 MOV PC, LR 指令返回主程序
    • 带状态切换的跳转指令 BX

      • 格式:BX<c> <label>
      • 执行 BX 指令时,若条件 c 满足,处理器会判断 label 指向的地址的 bit[0] 是否为 1,若为 1 则跳转时自动将 CPSR 寄存器的标志 T 置位,并将目标地址处的代码解释为 Thumb 代码来执行,即处理器会切换到 Thumb 状态;反之,若 label 指向的地址的 bit[0] 为 0,则跳转时自动将 CPSR 寄存器的标志 T 复位,并将目标地址处的代码解释为 ARM 代码来执行,即处理器会切换到 ARM 状态
      • 示例:
      .code 32
             ...
             ADR R0, thumbcode+1
             BX R0        @跳转到 thumbcode 处执行,并将处理器切换为 Thumb 模式
      thumbcode:
      .code 16
             ...
      
    • 带链接和状态切换的跳转指令 BLX

      • 格式:BLX<c> <label>
      • BLX 指令集合了 BL 与 BX 指令的功能。若条件 c 满足,除了会设置链接寄存器,还会根据 label 指向地址的 bit[0] 的值切换处理器状态
    • 块数据传输指令

      • 功能:用于一次处理一个块的数据传输
      • 常见的块数据传输指令:
        • STMDA

          • 格式:STMDA<c> <Rn>{!}, <registers>
          • 示例:STMIA R0!, {R1-R3} @将 R1 ~ R3 寄存器的内容存储到 R0 寄存器指向的存储单元
        • LDMDA

          • 格式:LDMDA<c> <Rn>{!}, <registers>
          • 示例:LDMIA R0!, {R1-R3} @从 R0 寄存器指向的存储单元读取 3 个字,将其放入 R1 ~ R3 寄存器
    • 软中断指令

      • 在汇编中使用较多的软中断指令是 SVC 指令。用户态的 ARM 程序通常将系统调用号传入 R7 寄存器,然后用 SVC 指令调用 0 号中断来直接执行系统调用
    • 协处理器指令

      • 上一节学过 SIMD 系列的 VMOVVADD 指令
    • 无条件执行指令

      • 使用较多的有 LDCSTC 指令
      • BLBLX 指令也属于无条件跳转指令