Android Protect-0.Dalvik语言基础简介

1. Dalvik 指 令格式

instruction-formats.html (下载再用浏览器打开)举了 Dalvik 指令的所有格式。

2. DEX 反汇编工具

目前 主流的 DEX 文件反汇编工具有 Android 官方的 dexdump 和第三方的 baksmali

dexdump 使用的都是以 “v’ 开头的寄存器, baksmali 则同时使用以 “v” 和 “p”开头的寄存器 ( 这涉及 Dalvik 寄存器的命名法)。 两者的寄存器表示法不同, baksmali 使用的是p 命名法dexdump 使用的是 v 命名法

Dalvik 虚拟机是基于寄存器架构的, 其代码中使用了大量的寄存器,Dalvik 使用的寄存器都是 32 位的, 支持所有类型。 对 64 位类型,可以用两个相邻的寄存器来表示, Dalvik 虚拟机最多支持65536个虚拟寄存器[v0, v65535]。

3. p 命名法

p 命名法解释如下:假设一个函数使用了 M个寄存器, 且该函数有N个参数。参数使用最后的 N 个寄存器,局部变量使用从 v0 开始的 M- N 个寄存器。

举例:

假定一个函数使 用了5个寄存器和2个显式的整型参数,该函数是 Hello 类的非静态方法,它在被调用时会传 入一个隐式的 Hello 对象引用(this指针) ,因此, 实际上传入了3个 参数。

根据传参规则,局部变量将使用前2个寄存器(v0,v1), 参数将使用后3个寄存器(p0,p1,p2), p0 用于表示被传入的 Hello 对象引用。

使用 p 命名法表示的 Dalvik 汇编代码, 通过寄存器的前缀更容易判断寄存器到底是局部变量寄存器还是参数寄存器, 所以大多是使用 p 命名法, 也就是采用smali 语法格式。

4. Dalvik 字节码

Dalvik 字节码有 自己的类型、方法及字段表示方法, 这些内容与Dalvik 虚拟机指令集一起组成了 Dalvik 汇编代码。

4.1 类型

Dalvik 字节码只有两种类型,分别是基本类型引用类型。Dalvik 使用这两种类型来表示Java语言的全部类型。除了对象和 数组属于引用对象,其他的 Java 类 型都属 于基本类型

Dalvik 字节码类型描述符表:

语法含义
Vvoid, 只用 于返回值类型
Zboolean
Bbyte
Sshort
Cchar
Iint
Jlong
Ffloat
Ddouble
Ljava类类型
[数组类型

每个Dalvik 寄存器都是 32 位的。对长度小于或等于 32 位 的类型 来说,只用一个寄存器就可以存放该类型的值, 而对JD 等 64 位的类型来说, 它们的值要使用相邻的两个寄存器来存储,例如 v0与 v1 、v3 与 v4。

L 类型可以表示Java 类型中的任何类。 这些类在 Java 代码中以 package.name.ObjectName 的方式被引用。 在 Dalvik 汇编代码中,它们以 “Lpackage/name/ObjectName;” 的形 式表示 (注意 :最后有一个分号)。

L 表示后面跟着一个 Java 类, package/name/ 表示对象所在的包, ObjectName 表示对象的名称,分号表示对象名结束。
例如, Ljava/lang/String; 相当于 java.lang. String( 代码中很容易忘记把.替换成/) 。

[类 型可以表示所有基本类型的数组。[后 面紧跟基本类型描述符,例如 “[I” 表示一个整型一维数组, 相当于 Java 中的 int[]
多个 “ [” 放在一起, 可以表示多维数组, 例如 “[[I” 表示 “int[][]”、 “[[[I” 表示 “int[][][]” 。
需要注意的是,多维数组的最大维数为 255。

L[ 可以同时使用,以表示对象数组。 例如, “[Ljava/lang/String;” 表示 Java 中的字符串数组。

4.2 方法

方法的表现形式要比类型复杂一些。Dalvik 使用方法名、 类型参数与返回值来 详细描述一个方法。一方面, 这样做有助于 Dalvik 虚拟机在运行时从方法表中快速找到正确的方法;另一方面,Dalvik 虚拟机可以使用它们来进行一些静态分析, 例如 Dalvik 字节码的验证与优化。方法的格式如下。

Lpackage/name/ObjectName; ->MethodName(III)Z

Lpackage/name/ObjectName;” 应该被理解为一个类型, MethodName 为具体的方法名,(III)Z是方法的签名 部分,括号内的 III 为方法的参数(在此为三个整型参数), Z 表示方法的返回类型( boolean 类型 ) 。

在这里插入图片描述

一个更为复杂的例子如下:

method (I[[IILjava/lang/String; [Ljava/lang/Object;)Ljava/lang/String;

将其转换成 Java 形式的代码, 具体如下:

String method(int,int[][],int,String,Object[])

baksmali 生成的方法代码以 .method 指令开始,以 .end method 指令结束, 根据方法类型的不同,在方法指令前可能会用来添加注释。例如, “# virtual methods" 表示这是一个虚方法,“# direct methods” 表示这是一个直接方法。
在这里插入图片描述

4.3 字段

字段与方法相似,只是字段没有方法签名域中的参数和返回值, 取代它们的是字段的类型。Dalvik 虚拟机在定位字段与进行字节码静态分析时会用到它。字段的格式如下。

Lpackage/name/ObjectName; ->FieldName:Ljava/lang/String;

字段 由类型(Lpackage/name/ObjectName; ) 、字段名 ( FieldName ) 与字段类型 ( Ljava/lang/String; ) 组成, 字段名与字段类型用冒号分开。
在这里插入图片描述
baksmali 生成的字段代码以 .field 指令开头, 根据字段类型的不同, 在字段指令前可能会"#"用来添加注释。例如, “#instance fields” 表示这是一个实例字段, “#static fields” 表示这是一个静态字段。
在这里插入图片描述

5. Dalvik 指令集

对于 Android 4.4 之前的系统,可以在 DexOpcodes.h 中 找到完整的 Dalvik 指令集。
对于 Android 4.4 及之后以 ART 为主导的系统, 可以在dex_instruction.h中找 到系统支持的完整的指令集定义。

从这两份指令集的定义文档 中可以 发现, Dalvik 指令集使用了单字节的指令助记符。

5.1 指令类型

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

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

以指令 move-wide/froml6 vAA, vBBBB 为例:

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

Dalvik 指令集中的大多数指令都使用寄存器作为目的操作数或源操作数。 Dalvik 指令集中的大多数指令都使用寄存器作为目的操作数或源操作数。
A、B、C、D、E、 F、G、H 代表 4 位的数值,可用于表示数值0 -15 或寄存器 v0~vl5;
AA、BB、CC、DD、EE、 FF、GG、HH 代表 8 位的数值,可用于表示数值0 -255 或寄存器 v0~v255;
AAAA、BBBB、CCCC、DDDD、EEEE、 FFFF、GGGG、HHHH 代表 16 位的数值,可用于表示数值0 -65535 或寄存器 v0~v65535;

所以对于v0~v15,它可以视为vAAvBBBB, 如 move-wide/froml6 v0, v1

5.2 空操作指令

空操作指令的助记符为 nop, 它的值为 00。 nop 指令通常用于对齐代码,不进行实际操作。

5.3 数据操作指令

数据操作指令为 move 其原型为 move destination, source
根据字节码大小与类型的不同,move 指令有不同的后缀, 列举如下。

  1. move vA, vB 指令用于将 vB 寄存器的值赋予 vA 寄存器,源寄存器与目的寄存器都为 4 位。
  2. move/froml6 vAA, vBBBB 指令用于将 vBBBB 寄存器的值赋予 vAA 寄存器,源寄存器为 16 位,目的寄存器为 8 位。
  3. move/16 vAAAA, vBBBB 指令用于将 vBBBB 寄存器的值赋予 vAAAA 寄存器,源寄存器与目的寄存器都为 16 位。
  4. move-wide vA, vB 指令用于为 4 位的寄存器赋值, 源寄存器与目的寄存器都为 4 位。
  5. move-wide/froml6 vAA, vBBBBmove-wide/16 vAAAA, vBBBB 指令用于使 move-wide 相同。
  6. move-object vA, vB 指令用于为对象赋值,源寄存器与目的寄存器都为 4 位。
  7. mov-object/froml6 vAA, vBBBB 指令用于为对象赋值,源寄存器是 16 位的, 目的寄存器是 8 位的。
  8. move-object/16 vAAAA, vBBBB 指令用于为对象赋值, 源寄存器与目的寄存 器都是 16 位的。
  9. move-result vAA 指令用于将上一个 invoke 类型指令操作的单字非对象结果赋予 vAA 寄存器。
  10. move-result-wide vAA 指令用于将上一个 invoke 类型指令 操作的双字非对象结果赋予 vAA寄存器。
  11. move-result-object vAA 指令用于将上一个 invoke 类型指令操作的对象结果赋予 vAA 寄存器。
  12. move-exception vAA 指令用于将一个在运行时发生的异常 保存到 vAA 寄存器中。这条指令必须在异常发生时由异常 处理器使用, 否则指令无效。

wide针对的是类型为 doublelong

5.4 返回指令

返回指令是指函数结 束时运行的最后一条指令, 它的基础字节码为 return 有如下四条返回指令。

  1. return-void 指令表示函数从一个 void 方法返回。
  2. return vAA 指令表示 函数返 回一个 32 位非对象类型的值, 返 回值为 8 位 寄存器 vAA
  3. return-wide vAA 指令表示 函数返 回一个 64 位非对象类型的值, 返 回值为 8 位寄存器 vAA
  4. return-object vAA 指令表示函数返回一 个对象类型的值, 返 回值为 8 位寄存器 vAA

5.5 数据定义指令

数据定义指令用于定义程序中用到的常量、字符串、类等数据, 它的基础字节码为 const 列举如下。

  1. const/4 vA, #+B 指令用于将数值符号扩展为 32 位后赋予寄存器 vA
  2. const/16 vAA, #+BBBB 指令用于将数值符号扩展为 32 位后赋予寄存器 vAA
  3. const vAA, #+BBBBBBBB 指令用于将数值赋予寄存器 vAA
  4. const/highl6 vAA, #+BBBB0000 指令用于将数值右边的 0 扩展为 32 位后赋予寄存器 vAA
  5. const-wide/16 vAA, #+BBBB 指令用于将数值符号扩展为 64 位后赋予寄存器 vAA
  6. const-wide/32 vAA, #+BBBBBBBB 指令用于将数值符号扩展为 64 位后赋予寄存器 vAA
  7. const-wide vAA, #+BBBBBBBBBBBBBBBB 指令用于将数值赋予寄存器 vAA
  8. const-wide/highl6 vAA, #+BBBB000000000000 指令用于将数值右边的 0 扩展为 64 位后赋予寄存器 vAA
  9. const-string vAA, string@BBBB 指令用于通过字符串索引构造一个字符串, 并将其赋予寄存器 vAA
  10. const-string/jumbo vAA, string@BBBBBBBB 指令用于通过字符 串索引 (较大 ) 构造一个字符串, 并将其赋予寄存器 vAA
  11. const-class vAA, type@BBBB 指令用于通过类型索引获取一个类 引用, 并将其赋予寄存器vAA
  12. const-class/jumbo vAAAA, type@BBBBBBBB 指令用于通过给定的类型索引获取一个类引用, 并将其赋予寄存器 vAAAA。 这条指令占用 2 字节, 值为 0x00ff(Android 4.0 中新增的指令 )。

很多人会被const/4, const/16搞晕,其实这应该只是davik虚拟机在解析dex文件的时候根据int值进行了优化, 如下例子:

    int testInt() {
        // smali: const v0, 0x7fffffff
        return Integer.MAX_VALUE; // const 默认4字节 32位
    }

    int test4Int() {
        // smali: const/4 v0, 0x1
        return 1; // const/4 半个字节 4位
    }

    int test16Int() {
        // smali: const/16 v0, 0x64
        return 100; // const/16 2个字节 16位
    }

    short testSHort() {
        // smali: const/16 v0, 0x7fff
        return Short.MAX_VALUE;// const/16 2个字节 16位
    }

    double testDouble() {
        // smali: const-wide v0, 0x7fefffffffffffffL    # Double.MAX_VALUE
        return Double.MAX_VALUE;// const-wide 8个字节 64位
    }

    double testLong() {
        // smali: const-wide/high16 v0, 0x43e0000000000000L    # 9.223372036854776E18
        return Long.MAX_VALUE;// const-wide 8个字节 64位
    }

    byte testByte() {
        // smali: const/16 v0, 0x7f
        return Byte.MAX_VALUE;// const/16  2个字节 16位
    }

5.6 锁指令

锁指令多用在多线程程序对 同一对 象的操作中。 Dalvik 指令集中如下有两条锁指令。

  1. monitor-enter vAA 指令用于为指定对象获取锁。
  2. monitor-exit vAA 指令用于释放指定对象的锁。

5.7 实例操作指令

与实例相关的操作包括实例的类型转换、检查及创建等,列举如下。

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

5.8 数组操作指令

数组操作包括获取数组长度、新建数组、数组赋值、数组元素取值与赋值等, 列举如下。

  1. array-length vA, vB 指令用于获取给定 vB 寄存器中数组的长度, 并将值赋予 vA 寄存器。 数组长度值就是数组中条目的个数。
  2. new-array vA, vB, type@CCCC 指令用于构造指定类型 ( type@CCCC ) 和大小 ( vB ) 的数组,并将值赋予 vA 寄存器。
  3. filled-new-array {vC, vD, vE, vF, vG}, type@BBBB 指令用于构造指定类型 (type@BBBB )和大小 (vA) 的数 组并填充数组内容。 vA 寄存器是隐含使用的,除了指定 数组的大小,还指定了参数的个数。vC~vG 是所使用的参数寄存器序列。
  4. filled- new- array/ range {vCCCC …vNNNN}, type@BBBB 指令的功能与 filled-new-array {vC,vD, vE, vF, vG}, type@BBBB 指令相同,只是参数寄存器使用 range 字节码后缀来指定取值范围。 vC 是第 1 个参数寄存器, N= A + C-1
  5. fill-array-data vAA, +BBBBBBBB 指令用指定的数据来填充数组,vAA 寄存器为数组引用(引用 的必须 是基础类型的数组),在指令后面会紧跟一个数据表。
  6. new-array/jumbo vAAAA, vBBBB, type@CCCCCCCC 指令的功能与 new-array vA, vB, type@CCCC指令相同,只是寄存器与指令索引的取值范围更大 (Android4.0 中新增的指令 )。
  7. filled -new- a r ray/ jumbo{ vCCCC... vNNNN}, type@BBBBBBBB 指令的功能与 filled -new- array/range {vCCCC ... vNNNN}, type@BBBB 指令 相同,只是前者 的指令索引的取值范 围更大( Android 4.0 中新增的指令 ) 。
  8. 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 、 aputboolean 、 aput-byte 、 aput-char aput-short。

5.9 异常指令

Dalvik 指令集中有一条用于抛出异常的指令, 具体如下。

  1. throw vAA 指令用于抛出 vAA 寄存器中指定类型的异常。

5.10 跳转指令

跳转指令用于从当前地址跳转到指定的偏移处。Dalvik 指令集中有三种跳转指令, 分别是无条件跳转指令 goto、 分支跳转指令 switch 与条件跳转指令 if

  1. goto +AA 指令用于无条件跳转到指定偏移处, 偏移量 AA 不能为 0

  2. goto/16 +AAAA 指令用于无条件跳转到指定偏移处,偏移量 AAAA 不能为 0

  3. goto/32 +AAAAAAAA 指令用于无条件跳转到指定偏移处。

  4. packed-switch vAA, +BBBBBBBB 分支跳转指令, vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个 packed-switch-payload 格式的偏移表,表中的值是递增的偏移量。

  5. sparse-switch vAA, +BBBBBBBB 分支跳转指令, vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个 sparse-switch-payload 格式的偏移表, 表中的值是无规律的偏移量。

  6. if-test vA, vB, +CCCC 条件跳转指令用于比较 vA 寄存器与 vB 寄存器的值。 如果比较结果满足,就跳转到 CCCC 指定的偏移处。偏移量 CCCC 不能为 0if-test 类型的指令如下:

    1. if-eq: 如果 vA 等于 vB 则跳转, 其 Java 语法表示为 “if(vA =vB)”
    2. if-ne: 如果 vA 不等于 vB 则跳转, 其 Java 语法表示为 “if(vA!=vB)”
    3. if-It: 如果 vA 小于 vB 则跳转, 其 Java 语法表示为 “if(vA<vB)”
    4. if-ge: 如果 vA 大于等于 vB 则跳转, 其 Java 语法表示为 “if(vA>=vB)”
    5. if-gt: 如果 vA 大于 vB 则跳转,其 Java 语法表示为 “if(vA>vB)” 。
    6. if-le: 如果 vA 小于等于 vB 则跳转 , 其 Java 语法表示为 “if(vA<=vB)” 。
  7. if-testz vAA, +BBBB 条件跳转指令将 vAA 寄存器的值与 0 进行 比较。 如果比较结果满足或值为 0, 就跳转到 BBBB 指定的偏移处。 偏移量 BBBB 不能为 0if-testz 类型的指令如下。

    1. if-eqz: 如果 vAA0 则跳转,其 Java 语法表示为 “if(!vAA)” 。
    2. if-nez: 如果 vAA 不为 0 则跳转 , 其 Java 语法表示为 “if(vAA)” 。
    3. if-ltz: 如果 vAA 小于 0 则跳转, 其 Java 语法表示为 “if(vAA<0)” 。
    4. if-gez: 如果 vAA 大于等于 0 则跳转, 其 Java 语法表示为 “if(vAA>=0)” 。
    5. if-gtz: 如果 vAA 大于 0 则跳转, 其 Java 语法表示为 “if(vAA>0)”。
    6. if-lez: 如果 vAA 小于等于 0 则跳转,其 Java 语法表示为 “if(vAA<=0)” 。

5.11 比较指令

比较 指令用于对两个寄存器的值 (浮点 型或长整型) 进行 比较, 格式为 cmpkind vAA, vBB,vCC, 其中 vBBvCC 是需要比 较的两个寄存器或两个寄存器对,比较的 结果将被放到 vAA 寄存器中。 Dalvik 指令集中共有五条比较指令, 列举如下。

  1. cmpl-float 指令用于比较两个单精度浮点数。 如果 vBB 寄存器的值大于 vCC 寄存器的值,则结果为 -1; 如果 vBB 寄存器的值等于 vCC 寄存器的值, 则结果为 0; 如果 vBB 寄存器的值小于 vCC 寄存器的值, 则结果为 1
  2. cmpg-float 指令用于比较两个单精度浮点数。 如果 vBB 寄存器的值大于 vCC 寄存器的值,则结果为 1; 如果 vBB 寄存器的值等于 vCC 寄存器的值, 则结果为 0; 如果 vBB 寄存器的值小于 vCC 寄存器的值, 则结果为 -1
  3. cmpl-double 指令用于比较两个双精度浮点数。 如果 vBB 寄存器的值大于 vCC 寄存器的值,则结果为 -1; 如果 vBB 寄存器的值等于 vCC 寄存器的值, 则结果为 0; 如果 vBB 寄存器的值小于 vCC 寄存器的值, 则结果为 1
  4. cmpg-double 指令用于比较两个双精度浮点数。 如果 vBB 寄存器的值大于 vCC 寄存器的值,则结果为 1; 如果 vBB 寄存器的值等于 vCC 寄存器的值, 则结果为 0; 如果 vBB 寄存器的值小于 vCC 寄存器的值, 则结果为 -1
  5. cmp-long 指令用于比较两个长整型数。 如果 vBB 寄存器的值大于 vCC 寄存器的值,则结果为 1; 如果 vBB 寄存器的值等于 vCC 寄存器的值, 则结果为 0; 如果 vBB 寄存器的值小于 vCC 寄存器的值, 则结果为 -1

5.12 字段操作指令

字段操作指令用于对对象实例的字段进行读写操作, 字段 的类型可以是 Java 中 有效的数据类型。 对普通字段操作与静态字段操作,有两种指令集 , 分别是 iinstanceop vA, vB, field@CCCCsstaticop vAA, field@BBBB

普通字段的指令前缀为 i, 例如,对普通字段,读操作使用 iget 指令,写操作使用 iput 指令。静态字段的指令前缀为s,例如,对静态字段, 读操作使用 sget 指令, 写操作使用 sput 指令。

根据访问的字段类型不同, 字段操作指令后面会紧跟字段类型的后缀。例如,iget-byte 指令表示读取实例字段的值的类型为字节型,iput-short 指令表示设置实例字段的值的类型为短整型。
这两类指令的操作结果是一样的 , 只是指令前缀与操作的字段类型不同。

  1. 普遍字段操作指令有 : iget、 iget-wide、 iget-object、iget-boolean 、 iget-byte 、igetchar 、iget-short 、 iput 、iput-wide 、 iput-object 、iput-boolean 、iput-byte 、iput-char 、iput-short。
  2. 静态字段操作 指令有 : sget、 sget-wide、 sget-object、sget-boolean 、 sget-byte、 sgetchar 、sget-short 、sput 、 sput-wide 、sput-object 、sput-boolean 、 sput-byte 、sput-char 、sput-short。

在 Android 4.0 中, Dalvik 指令集增加了 iinstanceop/ jumbo vAAAA, vBBBB, field@CCCCCCCCsstaticop/jumbo vAAAA, f ield@BBBBBBBB 两类指令。它们的作用与上面介绍的两类指令相同, 并在指令中增加了 jumbo 字节码后缀,且寄存器与指令索引的取 值范围更大。

5.13 方法调用指令

方法调用指令负责调用类实例的方法, 基础指令为 invoke
方法调用指令有 invoke-kind {vC,vD, vE,vF,vG}, meth@BBBBinvoke-kind/range {vCCCC ... vNNNN} ,meth@BBBB 两类。 这两类指令在作用上并无不同,只是后者在设置参数寄存器时使用 range 来指定寄存器的范围。根据方法类型的不同, 共有如下五条方法调用指令。

  1. invoke-virtualinvoke-virtual/range 指令用于调用实例的虚方法。
  2. invoke-superinvoke-super/range 指令用于调用实例的父类方法。
  3. invoke-directinvoke-direct/range 指令用于调用实例的直接方法。
  4. invoke-staticinvoke-static/range 指令用于调用实例的静态方法。
  5. invoke-interfaceinvoke-interface/range 指令用于调用实例的接口方法。

在 Android 4.0 中, Dalvik 指令集增加了 invoke- kind/jumbo {vCCCC…vNNNN}, meth@BBBBBBBB
这类指令。它与上面介绍的两类指令作用相同, 但是在指令中增加了 jumbo 字节码后缀, 且寄存
器与指令索引的取值范围更大。

方法调用指令的返回值必须用 move-result* 指令来获取, 示例如下。

invoke-static{}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
move-result-object v0

5.14 数据转换指令

数据转换指令用于将一种类型 的数值 转换成另一种类型的数值, 格式为 unop VA, vB。在 VB 寄存器或 vB 寄存器中存放了需要转换的数据。 转换结果保存在 vA 寄存器或 vA 寄存器中。数据转换指令列举如下。

  1. neg-int 指令用于对整型数求补。
  2. not-int 指令用于对整型数求反。
  3. neg-long 指令用于对长整型数求补。
  4. not-long 指令用于对长整型数求反。
  5. neg-float 指令用于对单精度浮点型数求补。
  6. neg-double 指令用于对双精度浮点型数求补。
  7. int-to-long 指令用于将整型数转换为长整型数。
  8. int-to-float 指令用于将整型数转换为单精度浮点型数。
  9. int-to-double 指令用于将整型数转换为双精度浮点型数。
  10. long-to-int 指令用于将长整 型数转换为整型数。
  11. long-to-float 指令用于将长整型数转换为单精度浮点型数。
  12. long-to-double 指令用于将长整型数转换为双精度浮点型数。
  13. float-to-int 指令用于将单精度浮点型数转换为整型数。
  14. float-to-long 指令用于将单精度浮点型数转换为长整型数。
  15. float-to-double 指令用于将单精度浮点型数转换为双精度浮点型数。
  16. double-to-int 指令用于将双精度浮点型数转换为整型数。
  17. double-to-long 指令用于将双精度浮点型数转换为长整型数。
  18. double-to-float 指令用于将双精度浮点型数转换为单精度浮点型数。
  19. int-to-byte 指令用于将整型转换为字节型。
  20. int-to-char 指令用于将整型转换为字符串。
  21. int-to-short 指令用于将整 型转换为短整型。

5.15 数据运算指令

数据运算指令包括算术运算指令与逻辑运算指令。 算术运算指令主要用于进行数值间的加 、减、 乘、除、 模、 移位等运算, 逻辑运算指令主要用于进行数值间的与、 或、 非、 异或等运算。

数据运算指令有如下四类 (由于数据运算可能在寄存器或寄存器对之间进行,下面讲解指令的作用时均使用寄存器来描述 )。

  1. binop vAA, vBB, vCC 指令用于将 vBB 寄存器与 vCC 寄存器进行运算, 将运算结果保存到 vAA 寄存器中。
  2. binop/2addr vA, vB 指令用于将 vA 寄存器与 vB 寄存器进行运算, 将运算结果保存到 vA 寄存器中。
  3. binop/litl6 vA, vB, #+CCCC 指令用于将 vB 寄存器与常量 CCCC 进行运算, 将运算结果保存到 vA 寄存器中。
  4. binop/lit8 vAA, vBB, #+CC 指令用于将 vBB 寄存器与常量 CC 进行运算, 将运算结果保存到 vAA 寄存器中。

后三类指令比第一类指令分别多出了 2addr、 litl6、 lit8 等指令后缀。在这四类指令中, 基础字节码相同的指令执行的运算是类似的。

在第一类指令中,根据数据类型的不同, 会在基础字节码后面加上不同的后缀,例如 -int-long 分别表示操作的数据类型为整型长整型。第一类指令可归类如下。

  1. add-type:vBB 寄存器的值与 vCC 寄存器的值进行加法运算 ( vBB + vCC ) 。
  2. sub-type:vBB 寄存器的值与 vCC 寄存器的值进行减法运算 ( vBB-vCC ) 。
  3. mul-type:vBB 寄存器的值与 vCC 寄存器的值进行乘法运算 ( vBB x vCC ) 。
  4. div-type:vBB 寄存器的值与 vCC 寄存器的值进行除法运算 ( vBB/vCC) 。
  5. rem-type:vBB 寄存器的值与 vCC 寄存器的值进行模运算 ( vBB % vCC ) 。
  6. and-type:vBB 寄存器的值与 vCC 寄存器的值进行与运算 ( vBB AND vCC ) 。
  7. or-type:vBB 寄存器的值与 vCC 寄存器的值进行或运算 ( vBB OR vCC ) 。
  8. xor-type:vBB 寄存器的值与 vCC 寄存器的值进行异或运算( vBB XOR vCC) 。
  9. shl-type:vBB 寄存器的值 (有符号数)左移 vCC 位 ( vBB << vCC ) 。
  10. shr-type:vBB 寄存器的值 (有符号数) 右移 vCC 位 (vBB >> vCC ) 。
  11. ushr-type:vBB 寄存器的值 (无符号数) 右移 vCC 位 ( vBB >> vCC ) 。

基础字节码后面的 -type 可以是 -int-long-float-double。 后三类指令与之类似,此处不再列出。

6. Dalvik 指令练习

6.1 编写 smali 文件

新建文本文件 HelloWorld.smali, 建议使用VSCode的扩展来编辑,可以提示错误 , 写出HelloWorld 类的程序框架,具体如下。

.class public LHelloWorld;                            #定义类名
.super Ljava/lang/Object;                              #定义父类
.method public static main([Ljava/lang/String;)V      #声明静态main()方法
    .registers 4                                      #程序使用v0, v1, v2寄存器和一个参数寄存器
    .prologue                                         #代码起始指令
    ###空指令
    nop
    nop
    nop
    #数据定义指令, v0=8,v1=5,v2=3
    const/16 v0, 0x8
    const/4  v1, 0x5
    const/4  v2, 0x3
    #数据操作指令,v1=v2=3
    move v1, v2
    #数组操作指令,v0=new int[8],v1 = v0.length=8
    new-array v0, v0, [I
    array-length v1, v0
    #实例操作指令,v1=new StringBuilder()
    new-instance v1, Ljava/lang/StringBuilder;
    #方法调用指令,v1调用StringBuilder构造函数
    invoke-direct {v1},Ljava/lang/StringBuilder;-><init>()V
    #跳转指令,如果v0!=0,就跳转到cond_0,否则跳转到goto_0
    if-nez v0, :cond_0
    goto :goto_0
    :cond_0
    #数据转换指令, v2= (float)3
    int-to-float v2, v2
    #数据运算指令, v2 = v2+v2 =(float)6
    add-float v2, v2, v2
    #比较指令, v2==v2所以v0=0
    cmpl-float v0, v2, v2
    #字段操作指令
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "Hello world" #构造字符串
    #方法调用指令, v0 = PrintStream.out, v1="Hello world"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    #返回指令
    :goto_0
    return-void                                       #返回空值
.end method

6.2 编译 smali 文件

编译 smali 文件时使用的工具是 smali,它的使用说明如下:https://github.com/JesusFreke/smali/wiki/SmaliBaksmali2.2

打开命令提示符窗口, 执行如下命令进行编译。

java -jar smali-2.3.4.jar assemble HelloWorld.smali -o HelloWorld.dex

如果编译没有错误, 会在当前目录下生成 HelloWorld.dex 文件, 否则会提示错误所在的行。

6.3 测试运行

启动 Android 运行环境 ( Android 模拟器和真实的 Android 设备均可), 在命令提示符窗口执
行如下命令,测试运行结果。

adb push HelloWorld.dex /sdcard/
adb shell dalvikvm -cp /sdcard/HelloWorld.dex HelloWorld

结果如下:
在这里插入图片描述

7. 16进制转换dex

可使用以下网址: https://armconverter.com/
如我们使用JEB打开一份so:
在这里插入图片描述
==>
在这里插入图片描述
如果我们想把BEQ改成BNE
==>
在这里插入图片描述
可以看出,直接把D0改成D1即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值