avatar

Catalog
Dalvik指令集

前言

前一篇中,学习了Dalvik可执行指令格式,对Dalvik指令有个大概的认识,本篇就在此基础上,学习并整理Dalvik指令集相关内容。

自Android 4.4以来,可以在Android的源码文件art/runtime/dexinstructionlist.h中找到系统支持的完整的指令集定义。可以参考此链接(Android 6.0.0_r5源码文件中指令集的定义)。从中可以发现,Dalvik指令集使用了单字节的指令助记符

Dalvik指令类型

Dalvik指令在调用格式上模仿了C语言的调用约定,指令的语法与助记符有如下特点:

  • 参数采用从目标(destination)到源(source)的方式
  • 32位常规类型的字节码未添加任何后缀。
  • 64位常规类型的字节码添加 -wide 后缀
  • 对特殊类型的字节码,根据具体类型添加后缀,可以是 -boolean、-byte、-char、-short、-int、-long、-float、-double、-object、-string、-class、-void 中的一个。
  • 根据字节码布局与选项的不同,为一些字节码添加了字节码后缀以消除歧义。这些后缀通过在字节码主名称添加斜杠来分隔
  • 在指令集的描述中,宽度值中的每个字母都表示4位的宽度。

来看下面一个例子:

Code
1
move-wide/from16 vAA, vBBBB
  • move:基础字节码(base opcode),表示这是一个基本操作。
  • -wide:名称后缀(name suffix),表示指令操作的数据宽度(64位)。
  • from16:字节码后缀(opcode suffix),表示源为一个16位的寄存器引用变量。
  • vAA:目的寄存器,始终在源的前面,取值范围v0~v255。
  • vBBBB:源寄存器,取值范围,v0~v65535。

Dalvik指令集中的大多数指令都使用寄存器作为目的操作数或源操作数,其中寄存器的表示有如下含义:

  • A、B、C、D、E、F、G、H 代表4位的数值,可用于表示数值 0~15 或寄存器 v0~v15。
  • AA、BB、CC、DD、EE、FF、GG、HH 代表8位的数值,可用于表示 0~255 或寄存器 v0~255。
  • AAAA、BBBB、CCCC、DDDD、EEEE、FFFF、GGGG、HHHH 代表16位的数值,可用于表示 0~63335 或寄存器 v0~v65535。

以上是指令类型的一些说明,下面将会介绍具体操作对应的Dalvik指令。

Dalvik常用指令

空操作指令

Code
1
2
3
指令:nop
对应的值:00
作用:通常用于对齐代码,不进行实际操作。

数据操作指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
指令:move
原型:move destination, source
指令后缀:根据字节码大小与类型的不同,后缀也有所不同

示例:
move vA, vB //将vB寄存器的值赋予vA寄存器,源寄存器与目的寄存器都为4位。
move/from16 vAA, vBBBB //将vBBBB寄存器的值赋予vAA寄存器,源寄存器为16位,目的寄存器为8位
move/16 vAAAA, vBBBB //将vBBBB寄存器的值赋予vAAAA寄存器,源寄存器、目的寄存器均为16位
move-wide vA, vB //为4位的寄存器对赋值,源寄存器与目的寄存器都为4位
move-wide/from16 vAA, vBBBB //用于使move-wide相同(这里没搞明白是啥意思)
move-object vA, vB //为对象赋值,源寄存器和目的寄存器均为4位
move-object/from16 vAA, vBBBB //为对象赋值,源寄存器是16位的,目的寄存器是8位的
move-object/16 vAAAA, vBBBB //为对象赋值,源寄存器与目的寄存器都是16位的
move-result vAA //将上一个invoke类型指令操作的单字非对象结果赋予vAA寄存器
move-result-wide vAA //将上一个invoke类型指令操作的双字非对象结果赋予vAA寄存器
move-result-object vAA //将上一个invoke类型指令操作的对象结果赋予vAA寄存器
move-exception vAA //将一个在运行时发生的异常保存到vAA寄存器中;这条指令必须在异常发生时由异常处理器使
//用,否则指令无效

返回指令

Code
1
2
3
4
5
6
7
8
指令:return
作用:作为函数结束时运行的最后一条指令

示例:
return-void //表示函数从一个void方法返回
retutn vAA //表示函数返回一个32位非对象类型的值,返回值为8位寄存器vAA
return-wide vAA //表示函数返回一个64位非对象类型的值,返回值为8位寄存器对vAA
return-object vAA //表示函数返回一个对象类型的值,返回值为8位寄存器vAA

数据定义指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
指令:const
作用:用于定义程序中用到的常量、字符串、类等数据

示例:
const/4 vA, #+B //将数值符号扩展为32位后赋予寄存器vA
const/16 vAA, #+BBBB //将数值符号扩展为32位后赋予寄存器vAA
const vAA, #+BBBBBBBB //将数值赋予寄存器vAA
const/high16 vAA, #+BBBB0000 //将数值右边的0扩展为32位后赋予寄存器vAA
const-wide/16 vAA, #+BBBB //将数值符号扩展为64位后赋予寄存器对vAA
const-wide/32 vAA, #+BBBBBBBB //将数值符号扩展为64位后赋予寄存器对vAA
const-wide vAA, #+BBBBBBBBBBBBBBBB //将数值赋予寄存器对vAA
const-wide/high16, #+BBBB000000000000 //将数值右边的0扩展为64位后赋予寄存器对vAA
const-string vAA, string@BBBB //通过字符串索引构造一个字符串,并将其赋予寄存器vAA
const-string/jumbo vAA, string@BBBBBBBB //通过字符串索引(较大)构造一个字符串,并将其赋予寄存器vAA
const-class vAA, type@BBBB //通过类型索引获取一个类引用,并将其赋予寄存器vAA
const-class/jumbo vAAAA, type@BBBBBBBB //通过给定的类型索引一个类引用,并将其赋予寄存器vAAAA。这条指令占用2字节
//,值为0x00ff(Android4.0新增指令)

锁指令

Code
1
2
3
4
5
6
指令:monitor
作用:用于多线程程序对同一对象操作

示例:
monitor-enter vAA //为指定对象获取锁
monitor-exit vAA //释放指定对象的锁

实例操作指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
作用:用于对实例的类型转换,检查及创建

示例:
check-cast vAA, type@BBBB //将vAA寄存器中的对象引用转换成指定的类型,若失败会抛出
//ClassCaseException异常。若类型B指定的是基本类型,则对非基本
//类型的类型A来说,运行将会失败
check-cast/jumbo vAAAA, type@BBBBBBBB //功能与上一条指令相同,加上字节码后缀jumbo后,寄存器与指令索引
//的取值范围更大(Android4.0新增)
instance-of vA, vB, type@CCCC //判断vB寄存器中的对象引用是否可以转换成指定的类型,如果可以就为
//vA寄存器赋值1,否则为vA寄存器赋值0
instance-of/jumbo vAAAA, vBBBB, type@CCCCCCCC //功能同上一条指令,寄存器与指令索引的取值范围更大
new-instance vAA, type@BBBB //构造一个指定类型对象的新实例,并将对象引用赋值给vAA寄存器。类型
//符type指定的类型不能是数组类
new-instance/jumbo vAAAA, type@BBBBBBBB //功能同上一条指令,寄存器与指令索引的取值范围更大

数组操作指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
作用:获取数组长度,新建数组,数组赋值,数组元素取值与赋值等

示例:
array-length vA, vB //获取给定vB寄存器中数组的长度,并将值赋予vA寄存器。
new-array vA, vB, type@CCCC //构造指定类型(type@CCCC)和大小(vB)的数组,并将值赋予vA寄存器
new-array/jumbo vAAAA, vBBBB, type@CCCCCCCC //功能同上一条指令,寄存器与指令索引的取值范围更大
filled-new-array {vC, vD, vE, vF, vG}, type@BBBB//构造指定类型(type@BBBB)和大小(vA)的数组并填充数组内容。vA寄存
//器是隐含使用的,除了指定数组的大小,还指定了参数的个数。vC~vG
//是所使用的参数寄存器序列
filled-new-array/range {vCCCC ... vNNNN}, type@BBBB
//功能与上一条指令相同,参数寄存器使用range字节码后缀来指定取
//值范围。vC是第1个参数寄存器,N=A+C-1
filled-new-array/jumbo {vCCCC ... vNNNN}, type@BBBBBBBB
//功能同上一条指令,指令索引的取值范围更大(注意这里寄存器取值范
//围不变)
fill-array-data vAA, +BBBBBBBB //用指定的数据来填充数组,vAA寄存器为数组引用(引用的必须是基础类
//型的数组),在指令后面会紧跟一个数据表。
arrayop vAA, vBB, vCC //指令用于对vBB寄存器指定的数组元素进行取值和赋值。vCC寄存器用于
//指定数组元素的索引。vAA寄存器用于存放读取的或需要设置的数组元
//素的值。读取元素时使用aget类指令,为元素赋值时使用aput类指令
//根据数组中存储的类型指令的不同,在指令后面会紧跟不同的指令后缀
//指令包括:aget、aget-wide、aget-object、aget-boolean、
//aget-byte、aget-char、aget-short、aput、aput-wide、
//aput-object、aput-boolean、aput-byte、aput-char、
//aput-short

异常指令

Code
1
2
3
4
5
指令:throw
作用:用于抛出异常

示例:
throw vAA //抛出vAA寄存器中指定类型的异常

跳转指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
指令:goto、switch、if
作用:从当前地址跳转到指定的偏移处

示例:
goto +AA //无条件跳转到指定偏移处,偏移量AA不能为0
goto/16 +AAAA //无条件跳转到指定偏移处,偏移量AAAA不能为0
goto/32 +AAAAAAAA //无条件跳转到指定偏移处
packed-switch vAA, +BBBBBBBB //分支跳转指令,vAA寄存器为switch分支中需要判断的值,BBBBBBBB指
//向一个 packed-switch-payload 格式的偏移表,表中的值是递增的
//偏移量
sparse-switch vAA, +BBBBBBBB //分支跳转指令,vAA寄存器为switch分支中需要判断的值,BBBBBBBB指
//向一个 sparse-switch-payload 格式的偏移表,表中的值是无规律
//的偏移量
if-test vA, vB, +CCCC //条件跳转指令用于比较vA寄存器与vB寄存器的值。如果条件满足,就跳
//转到CCCC指定的偏移处,偏移量CCCC不能为0
if-testz vAA, +BBBB //条件跳转指令将vAA寄存器的值与0进行比较。如果条件满足,就跳转到
//BBBB指定的偏移处,偏移量BBBB不能为0
if-eq //if(vA == vB)
if-ne //if(vA != vB)
if-lt //if(vA < vB)
if-le //if(vA <= vB)
if-ge //if(vA >= vB)
if-gt //if(vA > vB)
if-eqz //if(!vAA)
if-nez //if(vAA)
if-ltz //if(vAA < 0)
if-lez //if(vAA <= 0)
if-gtz //if(vAA > 0)
if-gez //if(vAA >= 0)

比较指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
指令:cmp
作用:对两个寄存器的值(浮点型或长整型)进行比较
格式:cmpkind vAA, vBB, vCC
说明: vBB与vCC是需要比较的两个寄存器或寄存器对,比较的结果放到vAA寄存器中

示例:
cmpl-float //比较两个单精度浮点数:(float比较的是寄存器)
// vBB > vCC, vAA = -1
// vBB == vCC, vAA = 0
// vBB < vCC, vAA = 1
cmpg-float //比较两个单精度浮点数:
// vBB > vCC, vAA = 1
// vBB == vCC, vAA = 0
// vBB < vCC, vAA = -1
cmp1-double //比较两个双精度浮点数:(double比较的是寄存器对)
// vBB > vCC, vAA = -1
// vBB == vCC, vAA = 0
// vBB < vCC, vAA = 1
cmpg-double //比较两个双精度浮点数:
// vBB > vCC, vAA = 1
// vBB == vCC, vAA = 0
// vBB < vCC, vAA = -1
cmp-long //比较两个长整型数:(long比较的是寄存器)
// vBB > vCC, vAA = 1
// vBB == vCC, vAA = 0
// vBB < vCC, vAA = -1

字段操作指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
指令:get/put
作用: 用于对对象实例的字段进行读写操作,字段类型可以是Java中有效的数据类型
普通字段指令集: iinstanceop vA, vB, field@CCCC
静态字段指令集: sstaticop vAA, field@BBBB
普通字段指令前缀: i,例如,普通字段,读操作用iget指令,写操作用iput指令
静态字段指令前缀: s,例如,静态字段,读操作用sget指令,写操作用sput指令
普通字段操作指令: iget、iget-wide、iget-object、iget-boolean、iget-byte、iget-char、iget-short、iput
、iput-wide、iput-object、iput-boolean、iput-byte、iput-char、iput-short
静态字段操作指令: sget、sget-wide、sget-object、sget-boolean、sget-byte、sget-char、sget-short、sput
、sput-wide、sput-object、sput-boolean、sput-byte、sput-char、sput-short
Android4.0新增指令集: iinstanceop/jumbo vAAAA, vBBBB, field@CCCCCCCC,
sstaticop/jumbo vAAAA, field@BBBBBBBB

方法调用指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
指令:invoke
作用:负责调用类实例的方法
指令类型:
1.不使用range指定寄存器的范围: invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
2.使用range指定寄存器的范围: invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB
3.Android4.0新增大范围寄存器与指令索引: invoke-kind/jumbo {vCCCC .. vNNNN}, meth@BBBB

示例:
invoke-virtual或invoke-virtual/range //用于调用实例的虚方法
invoke-super或invoke-super/range //用于调用实例的父类方法
invoke-direct或invoke-direct/range //用于调用实例的直接方法
invoke-static或invoke-static/range //用于调用实例的静态方法
invoke-interface或invoke-interface/range //用于调用实例的接口方法

返回值获取(move-result*):
invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
move-result-object v0

数据转换指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
格式:unop vA, vB
作用:将一种基本类型的数值转换成另一种基本类型的数值
vB:存放需要转换的数据的寄存器(对)
vA:存放转换结果的寄存器(对)

示例:
neg-int //对整型数求补
not-int //对整型数求反
neg-long //对长整型数求补
not-long //对长整型数求反
neg-float //对单精度浮点数求补
neg-double //对双精度浮点数求补
int-to-byte //整型->字节
int-to-char //整型->字符串
int-to--short //整型->短整型
int-to-long //整型数->长整型数
int-to-float //整型数->单精度浮点数
int-to-double //整型数->双精度浮点数
long-to-int //长整型数->整型数
long-to-float //长整型数->单精度浮点数
long-to-double //长整型数->双精度浮点数
float-to-int //单精度浮点数->整型数
float-to-long //单精度浮点数->长整型数
float-to-double //单精度浮点数->双精度浮点数
double-to-int //双精度浮点数->整型数
double-to-long //双精度浮点数->长整型数
double-to-float //双精度浮点数->单精度浮点数

数据运算指令

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
类型:算数运算、逻辑运算
作用:进行数值间的加、减、乘、除、模、移位、与、或、非、异或

数据运算指令4种类型:
binop vAA, vBB, vCC //将vBB寄存器与vCC寄存器进行运算,结果保存到vAA寄存器中
binop/2addr vA, vB //将vA寄存器与vB寄存器进行运算,结果保存到vA寄存器中
binop/lit16 vA, vB, #+CCCC //将vB寄存器与常量CCCC进行运算,结果保存到vA寄存器中
binop/lit8 vAA, vBB, #+CC //将vBB寄存器与常量CC进行运算,结果保存到vAA寄存器中

指令后缀:
1. 后3类指令比第1类指令多出了指令后缀,在binop相同的情况下,执行的运算是类似的
2. 第1类指令会根据数据类型的不同,增加不同的后缀,例如-int和-long

第1类指令归类:
add-type //vBB + vCC
sub-type //vBB - vCC
mul-type //vBB * vCC
div-type //vBB / vCC
rem-type //vBB % vCC
and-type //vBB AND vCC
or-type //vBB OR vCC
xor-type //vBB XOR vCC
shl-type //vBB(有符号数) << vCC
shr-type //vBB(有符号数) >> vCC
ushr-type //vBB(有符号数) >> vCC

参考资料

参考书籍:《Android软件安全权威指南》—— 丰生强

参考链接:

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/07/24/Dalvik%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
  • 微信
    微信
  • 支付寶
    支付寶