什么是dex文件

Python010

什么是dex文件,第1张

dex文件是Android平台上可执行文件的类型。

对于Android DEX文件进行优化,需要注意的一点是DEX文件的结构是紧凑的,但是我们还是要想方设法的进行提高程序的运行速度,我们就仍然需要对DEX文件进行进一步优化。

调整所有字段的字节序(LITTLE_ENDIAN)和对齐结构中的每一个域 验证DEX文件中的所有类 对一些特定的类进行优化,对方法里的操作码进行优化 。优化后的文件大小会有所增加,应该是原Android DEX文件的1-4倍。 优化发生的时机有两个:对于预置应用,可以在系统编译后,生成优化文件,以ODEX结尾。

这样在发布时除APK文件(不包含DEX)以外,还有一个相应的Android DEX文件;对于非预置应用,包含在APK文件里的DEX文件会在运行时被优化,优化后的文件将被保存在缓存中。

每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层操作系统而实现的。

Dex文件就是Dalvik可执行文件,实际上它就是一个优化后的java字节码文件,因此构造这类文件需要先写个java文件

Pino.java

然后编译

之后得到了Pino.class文件,之后我们用dx工具,该工具需要安装Android SDK才能有的工具

这样就得到了一个dex文件了,之后我们利用010editor工具来进行分析。

那我们从头开始分析

首先,我们来看一下Dex文件头的结构体

这里stringIdSize的值为0E,10进制就是14,也就是说这个dex文件的字符串的个数为14个,文件偏移是70,我们到70的位置看一下

蓝色部分就是DexStringId的内容了,每个字符串4字节,总共14个,我们先看一下第一组“76 01 00 00”,这个值并不是字符串的具体内容,而是字符串所在位置的文件偏移,我们去看一下176h这个位置

蓝色部分我一共选中了8个字节,其中第一个字节06代表的是之后多少个字节属于字符串,也就是“3C 69 6E 69 74 3E”,而最后一个字节的00其实是字符串结尾的空字节,但是计数的时候并没有算上而已,总结一下这个dex文件中所有的字符串如下:

先看一下第一个4字节的值“02 00 00 00 ”,对照之前我们整理的字符串的表格,就是LPino;即Pino类型的,整理一下所有的类型,如下

这里数量就是3,位置偏移为C4,跟过去看下

蓝色选中的部分就是所有的方法原型的结构了,这里又涉及到了一个新的数据结构

这三个属性分别是第一个是方法声明的字符串,第二个是方法的返回类型,第三个是方法的参数列表,其中DexTypeList是新的数据结构

回过头来看一下蓝色部分,12个字节,第一个4字节为8,说明DexStringId列表的索引是8,也就是V,第二个4字节是5,也就是V,最后一个是0,也就是没有参数,第一个方法就是void (),整理一下其他的如下:

也就是一个DexFieldId是8个字节

classIdx的值是4,也就是Ljava/lang/System,typeIdx的值是1,也就是Ljava/io/PrintStream,nameIdx的值是C,也就是out,总结一下字段如下:

也就是说每个DexMethodId占8个字节,第一个8字节中的classIdx的值是0,也就是LPino,protoIdx的值也是0,也就是void(),第三nameIdx也是0,也就是<init>,综合起来就是void Pino.<init>(),整理一下所有的方法如下:

上面的数据结构28个字节,内容的话看注释也能看懂,我们直接上实例,在这里,classIdx是1,也就是LPino,第二个accessFlags是1,也就是public,第三个superclassIdx是2,也就是父类是java.lang.Object,第四个interfacesOff是0,就是没有,第五个是sourceFileIdx是7,也就是Pino.java,第六个是annotationOff,是0,没有,第七个classData是22D,也就是DexClassData的偏移是22D,我们先来看看DexClassData的结构体

这里面又涉及到了其他三种结构体

这里需要注意的一点的就是这里的u4并不是值4字节,而是值uleb128的类型,具体是什么可以自行百度。

现在我们再去22D的位置看看

从这里可以判断姿态字段0个,实例字段0个,直接方法2个,虚方法0个。因为staticFields和instanceFields都是0个,所以直接从directMethods来看了,methodIdx为0,也就是void Pino.<init>(),accessFlags的值为“81 80 04”,这个是uleb128编码的,转换为16进制的话就是10001h,对照一下DexFile.h文件,知道方法是ACC_PUBLIC和ACC_CONSTRUCTOR

以下基于 Android_4.0.4源码进行分析。

先丢一张虚拟机用到的数据结构。我们的起点在

native private static int openDexFile(String sourceName, String outputName, int flags) throws IOException @ DexFile.java

重点 1

重点 2

@DvmDex.cpp

//根据 DexFile 的信息填充 DvmDex

static DvmDex* allocateAuxStructres(DexFile* pDexFile){

DvmDex* pDvmDex

const DexHeader* pHeader

u4 stringCount, classCount, methodCount, fileCount

pDvmDex = (DvmDex*) calloc(1, sizeof(DvmDex))

pDvmDex->pDexFile = pDexFile

pDvmDex->pHeader = pDexFile->pHeader

pHeader = pDvmDex->pHeader

stringCount = pHeader->stringIdsSize

classCount = pHeader->typeIdsSize

methodCount = pHeader->methodIdsSize

filedCount = pHeader->fileIdsSize

//根据实际的数量分配内存

pDvmDex->pResStrings = (struct StringObject ) calloc(stringCount, sizeof(struct stringObject ))

pDvmDex->pResClasses = (struct ClassObject ) calloc(ClassObject, sizeof(struct ClassObject ))

pDvmDex->pResMethods = (struct Method ) calloc(methodCount, sizeof(struct Method ))

pDvmDex->pResFilds = (struct Field ) calloc(fieldCount, sizeof(struct Field ))

...

pDvmDex->pInterfaceCache = dvmAllocAtomicCache(DEX_INTERFACE_CACHE_SIZE)

}

* 从上面看出来,DvmDex 包含 DexFile,DexFile 包含 DexHeader。通过解析 DexHeader,将常量池,方法区,字段等信息在内存的首地址存入 DexFile;然后通过 DexFile 的信息在 DvmDex 中分配合适的内存。于是 Dex 文件便初步解析完成了,并且保存在了内存。在进行类加载的时候,一个类的信息在内存中以 ClassObject 的结构保存。同时在 DexFile 中有一个 ClassLookup* pClassLookup 作为一个哈希表作为缓存保存已经加载了的类。

另外native private static int openDexFile(String sourceName, String outputName, int flags)的返回值是一个 int。明显这个 int 是对应 native 中一个对象的句柄。那么来看 Dalvik_dalvik_system_DexFile_openDexFile()@dalvik_system_DexFile.cpp 中的返回值是 DexOrJar。

于是在 DexFile 中的 cookie 保存的便是这个 DexOrJar 的地址。

https://github.com/Wi1ls/DexParse