详解odex中dex部分的变化原理

今年春节写代码度过的,也挺充实的。

这是假期的最后一篇啦。 :)



在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部分的变化原理_第1张图片
dvmContinueOptimization

上面的函数中,巴拉巴拉写了一堆的校验和重写(形成odex必要的一些数据),我们都忽略了,只关注dex部分的流程,如下:

详解odex中dex部分的变化原理_第2张图片
重写dex文件

其中rewriteDex又调用了下述函数:

详解odex中dex部分的变化原理_第3张图片
校验和优化所有的类

这里的verifyAndOptimizeClasses就是我们要找的函数了。

它遍历了class_def的列表,获取到了每个类的信息,并且对每个类都执行优化。

详解odex中dex部分的变化原理_第4张图片
校验和优化所有类

对于单个类的校验和优化的过程如下:

详解odex中dex部分的变化原理_第5张图片
校验和优化单个类

打住打住,这个函数对DexClassDef的accessFlags做了变更,分别在校验和优化后加入两个标志。

特殊类型
两个标志位的异或结果

这个结果不就是0x00030000吗? class_def段的对比如下,这个03终于找到了。。。

详解odex中dex部分的变化原理_第6张图片
class_def_diff



code段

继续阅读源码,看类是怎么优化的。

http://androidxref.com/4.4_r1/xref/dalvik/vm/analysis/Optimize.cpp

详解odex中dex部分的变化原理_第7张图片
优化单个类

上面这个函数遍历了它的直接方法和虚方法列表,对每个函数做优化。

详解odex中dex部分的变化原理_第8张图片
优化单个函数

上面这个函数就是最最核心的方法了,它对指令字节码部分做转换,转换后的指令码列表如下:

详解odex中dex部分的变化原理_第9张图片
快速标记指令

过程可以划分为针对instance_field,static_field, invoke_init, return_void, invoke_virtual, inline的几个部分。

详解odex中dex部分的变化原理_第10张图片
指令转换

转换instance_field

详解odex中dex部分的变化原理_第11张图片
实例属性

从上图可以看出,除了指令码更改为quick类型外,还会将原指令中的field_idx转换为byte_offset,这个byte_offset是偏移量可以直接定位。

这里还涉及到volatile类型,如果它是volatile类型的变量那么会把volatile_opc写入field_idx部分而不是byte_offset

详解odex中dex部分的变化原理_第12张图片
转换指令码

上图是如何转换指令,以为指令码只占用一个字节,所以通过异或来保留第二个字节的原有信息。 至于opcode >= 256基本是没用的,因为从上面的quick列表看,都在256以内。

转换过程还涉及到如何查找field,如下图所示。

详解odex中dex部分的变化原理_第13张图片
属性域转换

这个过程需要先查找类。

这个部分很难,说它难不是它在虚拟机中的加载难,而是没有任何依赖的情况下,如何加载并定位它,可是这是不可能的。。。 因为很多类,是rom自己编译好的,只能从rom对应的framework.odex, core.odex等系统类信息中去查询。

所以当我看到下面的referrer->classLoader,忽然就觉得,『有你真好!』

详解odex中dex部分的变化原理_第14张图片
寻找类

接着是一个递归查询的方法,它沿着类和它的父类一直往上查询,直到查询到field为止。

详解odex中dex部分的变化原理_第15张图片
寻找属性

转换static_field

同instance_field的转换类似。

注意一下,非volatile类型的static属性,是不做任何处理了。

看下面的逻辑,static_field被索引到后,只是判断了它的volatile类型信息。

详解odex中dex部分的变化原理_第16张图片
write_static

转换invoke_init

这是比较独特的一个转换,就是把构造函数的单独拎出来了,转换前后的数字很容易识别。

详解odex中dex部分的变化原理_第17张图片
invoke init

转换return_void

这个转换只涉及指令码转换

详解odex中dex部分的变化原理_第18张图片
return

这个转换过程是有条件的,从下面代码可以看出,如果函数所属的类是final类型或者它的属性中至少有一个是final类型的。

详解odex中dex部分的变化原理_第19张图片
转换return的条件

转换invoke_virtual

过程和instance_field类似,除了转换指令码,还会通过method_id找到对应的method方法,将它的method_index替换掉method_id。

详解odex中dex部分的变化原理_第20张图片
virtual

转换inline

同invoke_virtual的转换类似,不同的是,把method_id转换为对应的内联方法的索引。

详解odex中dex部分的变化原理_第21张图片
inline

从上面的转换过程来看,主要是把属性获取和设置,方法调用相关的指令字节码优化掉,这样虚拟机在加载odex后,在执行指令字节码的时候能更加快速的获取到相关信息。

对比结果分析

下面是code段对比的一个差异图,我们按照上面的转换过程一一对比下。

code段对比

先看下这段的反汇编信息:

(关于反汇编,参看:  用python一步步解剖dex文件(四)--- 反汇编框架)

详解odex中dex部分的变化原理_第22张图片
数据对应的反汇编信息

这里共有5个指令字节码单元,我们一个个分析。

第一段: 70 10 11 00 00 00

它是执行了方法,所以对应上面的『转换invoke_init』部分,按照逻辑分支又属于invoke_direct的分支,所以转换结果是 0xf0 | 0x100 = 0x1f0, 字节表示就是右侧的 f0 01。 其它不变化。

第二段: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




请勿转载,谢谢!

你可能感兴趣的:(详解odex中dex部分的变化原理)