大家都知道Java程序是运行在Java虚拟机上,Android程序呢? 虽然Android平台使用Java语言来开发应用程序,但是Android程序却不是运行在标准的Java虚拟机上的. Google为Android平台专门设计了一套虚拟机来运行Android程序–Dalvik Virtual Machine,也就是Dalvik虚拟机了
本篇作用:
扫盲Dalvik虚拟机了解Smail的语法,能读懂Smail文件Dalvik可执行文件的体积更小 稍作解析: SDK中有一个叫做dx的工具负责将Java字节码转换为Dalvik字节码.dx工具对Java类文件重新排列,消除在类文件中出现的所有冗余信息,避免虚拟机在初始化时出现重复的文件加载与解析过程.
举个栗子: 在Java中有大量的字符串常量在多个类文件中被重复使用,这些荣誉信息会直接增加文件的体积,同事也会严重影响虚拟机解析文件的效率.dx工具针对这个问题做了专门的处理,它将所有Java类文件中的常量池进行分解,消除其中的冗余信息,重新组合成一个常量池,所有的类文件共享同一个常量池.dx工具转换过程如图所示,由于dx工具对常量池的压缩,是的相同的字符串,常量在DEX文件中只出现一次,从而减小了文件的体积.
3. Java虚拟机与Dalvik虚拟机架构不同
简单说一下:Java虚拟机基于栈架构,Dalvik基于寄存器架构;
一般Dalvik汇编代码由一系列的Dalvik指令组成,指令语法由指令的位描述与指令格式标识来决定.位描述约定如下
每16位的字采用空格分割开来;每个字母表示四位,每个字母按顺序从高字节开始,排列到低字节.每四位之间可能用竖线”|”来表示不同的内容;顺序采用A-Z的翻个大写字母作为一个4位的操作码,op表示一个8位的操作码;“Ø”来表示这个字段的所有位为0值;栗子 “A|B|op BBBB F|E|D|C” 指令中间有两个空格,每个分开的部分是16位,共有3个16位组成这条指令; 第一个16位是”A|B|op” 高8位由A和B组成,低字节由操作码op组成; 第二个16位由BBBB组成,他表示一个16位的偏移值; 第三个16位分别由F,E,D,C共四个字节组成,在这里他们表示寄存器的参数.
单独使用位标识还无法确定一条指令的意思,必须通过指令格式标识来指定指令的格式编码,约定如下
指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母;第一个数字式标识指令由多少个16位的字组成;第二个数字标识指令最多使用寄存器的个数,特殊标记”r”标识使用一定范围内的寄存器;第三个字母为类型码,标识指令用到的额外数据的类型,见下图:还有一种特殊的情况 是末尾可能还会多出另一个字母,如果是”s”表示指令采用静态链接,如果是”i”表示指令应该被内联处理.
栗子
“22x” 有三条信息可以读出
指令由2个16位字组成指令使用2个寄存器没有使用到额外的数据另外,Dalvik指令对语法做了一些说明,约定如下
每条指令从操作码开始,后面紧跟参数,参数个数不定,每个参数之间采用逗号分开;每条指令的参数从指令的第一部分开始,op位于低8位,高8位可以是一个8位的参数也可以是两个4位的参数,还可以为空.如果指令超过16位,则后面的部分依次作为参数;如果参数使用”vX”的方式标识,表明它是一个寄存器,如v0,v1等;如果参数采用”#+X”的方式,表明它是一个常量数字;如果参数采用”+X”的方式,表明它是一个相对指令的地址偏移;如果参数采用”kind@X”的方式,表明它是一个常量池索引值.其中kind表示常量池类型,例如string@BBBB,表示的就是字符串常量池索引BBBB;栗子 “op vAA string@BBBB” 高8位为空,用到1个寄存器参数vAA,还用到一个字符串常量池索引BBBB;
扫盲结束了,开始重点了
Dalvik指令在调用格式上模仿了C语言的调用约定.Dalvik指令语法与助词符有如下特点:
参数采用从目标(destination)到源(source)的方式;根据字节码的布局与选项的不同,一些字节码添加了字节码后缀消除歧义,这些后缀通过在字节码主名称后添加斜杠”/”来分隔开;在指令集的描述中,宽度值中的每个字母表示宽度为4位;根据字节码的大小与类型的不同,一些字节码添加了名称后缀以消除歧义: 32位常规类型的字节码,未添加任何后缀;64位常规类型的字节码以-wide后缀;特殊类型的字节码根据具体类型添加后缀,他们可以是-boolean,-byte,-char,-short,-int,-long,-float,-double,-object,-string,-class,-void之一;栗子 “move-wide/from16 vAA,vBBBB”
move为基础字节码.标识这是基本操作; wide为名称后缀.标识指令操作的数据宽度(64位); from16位字节码后缀.标识源为一个16位的寄存器引用变量; vAA为目的寄存器,它始终在源的前面 取值范围为v0-v2^8-1(255); vBBBB为源寄存器,取值范围为v0-v2^16-1(65535)
空操作指令
空操作指令的助记符为nop,他的值是00,通常nop指令被用过对齐代码用途,没啥大用;
数据操作指令
数据操作指令为move.move指令的原型为move destination,source或者move destination,move指令根据字节码的大小与类型不同,后面会跟上不同的后缀. 栗子(表示太多直接上图,都差不多)
返回指令
返回指令指的是函数结尾时运行的最后一条指令.他的基础字节码位return,共有以下四条返回指令 栗子
“return-void” 返回一个void“return vAA” 返回一个32位非对象类型的值,返回值寄存器位8位的寄存器vAA;“return-wide vAA” 返回一个64位非对象类型的值,返回值寄存器位8位的寄存器vAA;“return-object vAA” 返回一个对象类型的值,返回值寄存器位8位的寄存器vAA;数据定义指令
数据定义指令用来定义程序中用到的变量,字符串,类等数据,他的基础字节码为const 栗子(表示太多直接上图-_-)
锁指令
锁指令多用在多线程程序中对同一对象的操作,Dalvik指令集中有两条锁指令.
“monitor-enter vAA” 为指定的对象获取锁“monitor-exit vAA”为指定的对象释放锁实例操作指令
与实例相关的操作包括实例的类型传换,检查及新建等;
“check-case vAA,type@BBBB” 将vAA中的对象引用强转为BBBB类型; (BBBB)vAA; “instance-of vA,vB,type@CCCC” 判断vB中的对象引用是否能转成CCCC类型,能vA=1,不能vA=0; if(vB.instanceof(type@CCCC)){ vA =1; }else{ vA = 0; } “new-instance vAA,type@BBBB” 新建一个BBBB的对象vAA,BBBB不能为数组 BBBB vAA = new BBBB(); “check-cast/jumbo vAAAA,type@BBBBBBBB” 与”check-case vAA,type@BBBB”作用相同,只是取值范围更大(Android 4.0新增)“instance-of/jumbo vAAAA,vBBBB,type@CCCCCCCC”与”instance-of vAA,vBB,type@CCCC”作用相同,只是取值范围更大(Android 4.0新增)“new-instance/jumbo vAAAA,type@BBBBBBBB”与”new-instance vAA,type@BBBB”作用相同,只是取值范围更大(Android 4.0新增)数组操作指令
数组操作包括获取数组长度(指的是数组的条目个数),新建数组,数组赋值,数组元素取值与赋值等操作;
“array-length vA,vB” vA = vB.length; // 将vB的长度赋值给vA “new-array vA,vB,type@CCCC” vA = CCCC[vB]; // 构建一个vB大的CCCC类型的数组赋值给vA 其余的附图异常指令
Dalvik 指令集中有一条指令用于抛出异常
“throw vAA” 抛出vAA寄存器中指定类型的异常跳转指令
Dalvik指令集中有三种跳转指令:无条件跳转(goto),分支跳转(switch),条件跳转(if)
“goto +AA” 无条件跳转到指定偏移处,偏移量AA不能为0;“goto/16+AAAA” 无条件跳转到指定偏移处,偏移量AAAA不能为0;“goto/32+AAAAAAAA” 无条件跳转到指定偏移处;“packed-switch vAA,+BBBBBBBB” 分支跳转指令. vAA寄存器为switch分支中需要判断的值即(switch(vAA)),BBBBBBBB指向一个packed-switch-payload格式的偏移表,表中的值是规律递增的.(先这么记住就好,感兴趣可以找百度..)“sparse-switch vAA,+BBBBBBBB”分支跳转指令,vAA寄存器为switch分支中需要判断的值即(switch(vAA)),BBBBBBBB指向一个sparse-switch-payload格式的偏移表,表中的值是无规律的偏移量.“if-test vA,vB,+CCCC” 条件跳转指令,比较vA与vB的值,如果比较结果满足就跳转到CCCC指定的偏移处,偏移量CCCC不能为0,if-test类型的指令有以下几条: “if-eq vA, vB, :cond_xx” 如果vA等于vB则跳转到:cond_xx “if-ne vA, vB, :cond_xx” 如果vA不等于vB则跳转到:cond_xx “if-lt vA, vB, :cond_xx” 如果vA小于vB则跳转到:cond_xx “if-ge vA, vB, :cond_xx” 如果vA大于等于vB则跳转到:cond_xx “if-gt vA, vB, :cond_xx” 如果vA大于vB则跳转到:cond_xx “if-le vA, vB, :cond_xx” 如果vA小于等于vB则跳转到:cond_xx “if-testz vAA,+BBBB”条件跳转指令,那vAA与0作比较,满足结果或者不满足结果就跳转到BBBB的指定偏移处BBBB不能为0, if-testz类型的指令有以下几条: “if-eqz vA, :cond_xx” 如果vA等于0则跳转到:cond_xx “if-nez vA, :cond_xx” 如果vA不等于0则跳转到:cond_xx “if-ltz vA, :cond_xx” 如果vA小于0则跳转到:cond_xx “if-gez vA, :cond_xx” 如果vA大于等于0则跳转到:cond_xx “if-gtz vA, :cond_xx” 如果vA大于0则跳转到:cond_xx “if-lez vA, :cond_xx” 如果vA小于等于0则跳转到:cond_xx比较指令
比较指令用于对两个寄存器的值(浮点型或者长整型)进行比较格式为: “cmpkind vAA,vBB,vCC”
Dalvik指令集中共有5条比较指令:
“cmpl-float” 比较两个float值; if(vBB == vCC){ vAA =0; }else if(vBB>vCC){ vAA = -1; }else if(vBB<vCC>){ vAA = 1; } “cmpg-float” 比较两个float的值 if(vBB == vCC){ vAA =0; }else if(vBB<vCC){ vAA = -1; }else if(vBB>vCC>){ vAA = 1; }当cmpg或者cmp时,B > C时A = 1,反之-1;当cmpl时,B > C时A = -1反之1;
“cmpg-double” 比较两个double的值“cmpl-double” 比较两个double的值“cmp-long” 比较两个long的值字段操作指令
字段操作指令用来对对象实例的字段进行读写操作. 字段的类型可以是Java中有效的数据类型,对普通字段与静态字段操作有两种指令集,分别是”iinstanceop vA,vB,field@CCCC”与”sstaticop vAA,field@BBBB”. 在Android 4.0系统中,有”iinstanceop /jumbovAAAA,vBBBB,field@CCCCCCCC”与”sstaticop/jumbo vAAAA,field@BBBBBBBB”.和上面的两种作用相同,只是加了jmpbo后缀,寄存器与指令索引取值范围更大(后面的只会说有/jumbo指令后缀的指令集,作用就不指明了) 普通字段指令的指令前缀为i,如.对普通字段读操作使用iget指令,写操作使用iput指令;静态字段的指令前缀为s,如.对静态字段的读操作为sget,写操作为sput; 根据访问的字段类型不同,字段操作指令后面会紧跟字段类型的后缀,如iget-byte指令表示读取实例字段的值类型为byte;
方法调用指令 方法调用指令负责调用类实例的方法,它的基础指令为invoke,方法调用指令有”invoke-kind{vC,vD,vE,vF,vG},meth@BBBB”与”invoke-kind/range{vCCCC…VNNNN},meth@BBBB”两类,这两类指令作用没啥不同,后者在设置参数寄存器时使用了range来指定寄存器的范围,根据方法类型的不同,共有如下5条方法调用指令:
“invoke-virtual” 调用实例的虚方法“invoke-super” 调用实例的父类方法“invoke-direct” 调用实例的直接方法“invoke-static” 调用实例的静态方法“invoke-interface” 调用实例的接口方法 Android 4.0有jumbo的指令集;方法调用指令的返回值必须使用move-result*指令来获取: invoke-static{},Landroid/os/Parcel;->obtain()Landroid/os/Parcel; move-result-object v0;
数据转换指令 数据转换指令用于将一种类型的数值转换为另一种类型,他的格式为”unop vA,vB” 把vB中的数据做一定运算(转换)放在vA中:(比较简单,直接上图)
数据运算指令
数据运算指令包括算数运算指令与逻辑运算指令:
算数运算指令:加,减,乘,除,模,移位等逻辑运算指令:间与,或,非,抑或等; 上个图吧 其中基础字节码后面的-type可以是-int,-long,-float,-double,后面3类指令也差不多,就不列了,触类旁通;== == == == == == == == == == == == == == == == == == == == == ==