今年春节写代码度过的,也挺充实的。
这是假期的最后一篇啦。 :)
引
在odex格式的解析文章中提到过,它的dex文件部分和原dex相比发生了变更,参见:
用python一步步解剖dex文件(五)--- odex格式
这里的变更共有3部分,分别是class_def段,code段和header中的checksum。
而且变更后的dex部分和原文件大小一致,变更的内容也不是很多。
到底发生了什么呢,我们还是从源码中找下,dexopt过程中到底对原dex文件做了什么。
class_def段
dexopt过程包含内容比较多,我们直接跳到核心优化的代码点吧。。。
http://androidxref.com/4.4_r1/xref/dalvik/vm/analysis/DexPrepare.cpp
上面的函数中,巴拉巴拉写了一堆的校验和重写(形成odex必要的一些数据),我们都忽略了,只关注dex部分的流程,如下:
其中rewriteDex又调用了下述函数:
这里的verifyAndOptimizeClasses就是我们要找的函数了。
它遍历了class_def的列表,获取到了每个类的信息,并且对每个类都执行优化。
对于单个类的校验和优化的过程如下:
打住打住,这个函数对DexClassDef的accessFlags做了变更,分别在校验和优化后加入两个标志。
这个结果不就是0x00030000吗? class_def段的对比如下,这个03终于找到了。。。
code段
继续阅读源码,看类是怎么优化的。
http://androidxref.com/4.4_r1/xref/dalvik/vm/analysis/Optimize.cpp
上面这个函数遍历了它的直接方法和虚方法列表,对每个函数做优化。
上面这个函数就是最最核心的方法了,它对指令字节码部分做转换,转换后的指令码列表如下:
过程可以划分为针对instance_field,static_field, invoke_init, return_void, invoke_virtual, inline的几个部分。
转换instance_field
从上图可以看出,除了指令码更改为quick类型外,还会将原指令中的field_idx转换为byte_offset,这个byte_offset是偏移量可以直接定位。
这里还涉及到volatile类型,如果它是volatile类型的变量那么会把volatile_opc写入field_idx部分而不是byte_offset
上图是如何转换指令,以为指令码只占用一个字节,所以通过异或来保留第二个字节的原有信息。 至于opcode >= 256基本是没用的,因为从上面的quick列表看,都在256以内。
转换过程还涉及到如何查找field,如下图所示。
这个过程需要先查找类。
这个部分很难,说它难不是它在虚拟机中的加载难,而是没有任何依赖的情况下,如何加载并定位它,可是这是不可能的。。。 因为很多类,是rom自己编译好的,只能从rom对应的framework.odex, core.odex等系统类信息中去查询。
所以当我看到下面的referrer->classLoader,忽然就觉得,『有你真好!』
接着是一个递归查询的方法,它沿着类和它的父类一直往上查询,直到查询到field为止。
转换static_field
同instance_field的转换类似。
注意一下,非volatile类型的static属性,是不做任何处理了。
看下面的逻辑,static_field被索引到后,只是判断了它的volatile类型信息。
转换invoke_init
这是比较独特的一个转换,就是把构造函数的
转换return_void
这个转换只涉及指令码转换
这个转换过程是有条件的,从下面代码可以看出,如果函数所属的类是final类型或者它的属性中至少有一个是final类型的。
转换invoke_virtual
过程和instance_field类似,除了转换指令码,还会通过method_id找到对应的method方法,将它的method_index替换掉method_id。
转换inline
同invoke_virtual的转换类似,不同的是,把method_id转换为对应的内联方法的索引。
从上面的转换过程来看,主要是把属性获取和设置,方法调用相关的指令字节码优化掉,这样虚拟机在加载odex后,在执行指令字节码的时候能更加快速的获取到相关信息。
对比结果分析
下面是code段对比的一个差异图,我们按照上面的转换过程一一对比下。
先看下这段的反汇编信息:
(关于反汇编,参看: 用python一步步解剖dex文件(四)--- 反汇编框架)
这里共有5个指令字节码单元,我们一个个分析。
第一段: 70 10 11 00 00 00
它是执行了
第二段:5b 01 0b 00
它是执行iput-object指令,对应上面的『转换instance_field』部分。
这个部分更改两处,一个是指令码变更为0xf7,另一个就是转换field_idx。
field_idx是字节的0b 00部分,也就是0x000b = 11,在field_id_list中的索引号为11。
经过虚拟机的类属性查询,它转换后的数值为08.
第三段:5b 02 0c 00
同第二段,指令码转换为0xf7,而field_idx经过转换后数值不变。
第四段: 0e
这个是return-void类型,因为原函数的所属类,没有final信息,所以不转换。
第五段: 00
nop类型,啥也不动。
这样,code段的变化就都解释清楚了。
checksum
最后的最后,odex对dex部分做checksum校验并重写。
这里有做checksum计算的示例:
https://github.com/callmejacob/dexfactory/blob/master/dexinfo.py
请勿转载,谢谢!