第七章 ARM 反汇编基础(五)(ARM 汇编指令集)
程序员文章站
2022-06-09 08:43:09
...
ARM 汇编指令集
- Android 平台的 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 指令进行划分,最基本的域有 cond、op1、op。这三个域的取值会直接影响指令的具体类别与格式。cond 域占用 28 位到 31 位,共四位,记为
bits[31:28]
,每个位记录了一个条件标志(Conditon Flag)。这四个标志位具体如下:- N:
bit[31]
,负数标志 - Z:
bit[30]
,0 标志 - C:
bit[29]
,进位标志 - V:
bit[28]
,溢出标志
- N:
- 每条 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 为例,属于分支、带链接的分支与块数据传输指令系列,对应的指令格式:
- 可看出,指令的位域分配有了变化,
bits[25:20]
的 op 域、bits[19:16]
的 Rn 域及bit[15]
的 R 域的不同组合,可表示不同的指令。若 op 域为 001001,表示的是 LDM(LDMIA、LDMFD)指令。跟进 LDM 指令的格式:
- 可看到,指令本质上是一系列二进制位的数据。但在编写汇编程序时,不可能直接用二进制值,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 汇编指令 - 打开计算器,查看其对应的二进制位信息:
- 首先是 cond 域
bits[31:28]
,其取值不是 0b1111,表示这不是一条无条件执行指令 - 接着是 op1 域
bits[27:25]
,其取值为 0b000,表示这是一条数据处理与杂项指令。数据处理与杂项指令的格式:
-
bit[25]
的 op 域为 0,bits[24:20]
的 op1 域为 0b11010,bits[7:4]
的 op2 域为 0。查阅 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>
- 完整的指令格式的域分布:
-
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
指令的十六进制值:
- 输出的 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 系列的
VMOV
、VADD
指令
- 上一节学过 SIMD 系列的
-
无条件执行指令
- 使用较多的有
LDC
、STC
指令 -
BL
、BLX
指令也属于无条件跳转指令
- 使用较多的有
-