学习
实践
活动
专区
工具
TVP
写文章

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

在odex格式的解析文章中提到过,它的dex文件部分和原dex相比发生了变更,参见:

这里的变更共有3部分,分别是class_def段,code段和header中的checksum。

而且变更后的dex部分和原文件大小一致,变更的内容也不是很多。

到底发生了什么呢,我们还是从源码中找下,dexopt过程中到底对原dex文件做了什么。

class_def段

dexopt过程包含内容比较多,我们直接跳到核心优化的代码点吧。。。

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

dvmContinueOptimization

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

重写dex文件

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

校验和优化所有的类

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

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

校验和优化所有类

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

校验和优化单个类

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

特殊类型

两个标志位的异或结果

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

class_def_diff

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类型信息。

write_static

转换invoke_init

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

invoke init

转换return_void

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

return

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

转换return的条件

转换invoke_virtual

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

virtual

转换inline

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

inline

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

对比结果分析

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

code段对比

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

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

数据对应的反汇编信息

这里共有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

请勿转载,谢谢!

欢迎关注

简书主页:https://www.jianshu.com/u/d2a08403ea7f

微信公众号:一叶谷

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180221G0CP5M00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

关注

腾讯云开发者公众号
10元无门槛代金券
洞察腾讯核心技术
剖析业界实践案例
腾讯云开发者公众号二维码

扫码关注腾讯云开发者

领取腾讯云代金券