前言
在前一篇中,学习了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位的宽度。
来看下面一个例子:
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常用指令
空操作指令
1 2 3
| 指令:nop 对应的值:00 作用:通常用于对齐代码,不进行实际操作。
|
数据操作指令
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寄存器中;这条指令必须在异常发生时由异常处理器使 //用,否则指令无效
|
返回指令
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
|
数据定义指令
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新增指令)
|
锁指令
1 2 3 4 5 6
| 指令:monitor 作用:用于多线程程序对同一对象操作
示例: monitor-enter vAA //为指定对象获取锁 monitor-exit vAA //释放指定对象的锁
|
实例操作指令
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 //功能同上一条指令,寄存器与指令索引的取值范围更大
|
数组操作指令
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
|
异常指令
1 2 3 4 5
| 指令:throw 作用:用于抛出异常
示例: throw vAA //抛出vAA寄存器中指定类型的异常
|
跳转指令
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)
|
比较指令
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
|
字段操作指令
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
|
方法调用指令
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
|
数据转换指令
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 //双精度浮点数->单精度浮点数
|
数据运算指令
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软件安全权威指南》—— 丰生强
参考链接: