Dex是Dalvik Executable的简称
Dex文件包含了编译java生成的.class文件,由dx工具将java字节码转化为smali字节码
apk安装后,对于dalvik第一次运行系统完成dex优化,转化为odex文件,存放在/data/dalvik-cache目录下,执行时加载odex文件到内存,art虚拟机是转成oat文件,都是在前后加些
odex优化仅在dex文件之上添加头和数据
整体结构:
文件头,字符串列表,类型列表,原型列表,字段列表,方法列表,类定义列表,数据
文件头
struct DexHeader{
u1 magic[8]; //dex\n035
u4 checksum; //adler32校验
u1 signature[kSHA1DigestLen]; //JAVA的SHA-1签名
u4 fileSize; //文件大小,整个文件多大。要对齐么?
u4 headerSize; //DexHeader结构大小
u4 endianTag; //字节序标记
u4 linkSize; u4 linkOff; //链接段大小和偏移
u4 mapOff; //DexMapList文件偏移
u4 stringIdsSize; u4 stringIdsOff;//这些表的大小和在文件中的偏移
u4 typeIdsSize; u4 typeIdsOff;
u4 protoIdsSize; u4 protoIdsOff;
u4 fieldIdsSize; u4 fieldIdsOff;
u4 methodIdsSize; u4 methodIdsOff;
u4 classDefsSize; u4 classDefsOff;
u4 dataSize; u4 dataOff;
};
字符串和类型列表
struct DexStringId{
u4 stringDataOff; //指向字符串数据起始地址,每一项指向字符串的起始地址
};
struct DexTypeId{
u4 descriptorIdx; //指向DexStringId列表的索引,就是有几种类型,就几个变量
};
struct DexProtoId{
u4 shortyIdx; // 指向DexStringId列表的索引,是方法声明字符串,就是参数返回值的缩写
u4 returnTypeIdx; // 指向DexTypeId列表的索引,返回值的类型,所以肯定也是指向typeid中的一项
u4 parametersOff; // 指向DexTypeList的偏移,参数
};
struct DexTypeList{
u4 size; // DexTypeItem的个数
DexTypeItem list[1];
};
struct DexTypeItem{
u2 typeIdx; // 指向DexTypeId列表的索引
};
字段和方法列表
struct DexFieldId{
u2 classIdx; //字段类类型,指向DexTypeId列表索引,字段属于哪个类
u2 typeIdx; //字段类型,指向DexTypeId列表索引,字段的类型
u4 nameIdx; //字段名,指向DexStringId列表索引,字段的名字
};
struct DexMethodId{
u2 classIdx; //类类型,指向DexTypeId列表索引
u2 protoIdx; //声明类型,指向DexTypeId列表索引
u4 nameIdx; //方法名,指向DexStringId列表索引
};
类列表
struct DexClassDef{
u4 classIdx; //类类型,指向DexTypeId列表索引
u4 accessFlags; //访问标志,ACC_为前缀的枚举值,就是public那些东西
u4 superclassIdx; //父类类型,指向DexTypeId列表索引
u4 interfacesOff; //接口,指向DexTypeList
u4 sourceFileIdx; //源文件名,指向DexStringId列表索引
u4 annotationsOff; //注解,指向DexAnnotationsDirectoryItem
u4 classDataOff; //指向DexClassData结构偏移
u4 staticValuesOff; //指向DexEncodedArray结构偏移
};
struct DexClassData{
DexClassDataHeader header; //字段和方法个数的描述
DexField* staticFields; //静态字段
DexField* instanceFields; //实例字段
DexMethod* directMethods; //直接方法
DexMethod* virtualMethods; //虚方法
}
struct DexClassDataHeader{
u4 staticFieldsSize; //静态字段个数
u4 instanceFieldsSize; //实例字段个数
u4 directMethodsSize; //直接方法个数
u4 virtualMethodsSize; //虚方法个数
};
struct DexField{
u4 fieldIdx; //DexFieldId列表索引
u4 accessFlags; //访问标志
};
struct DexMethod{
u4 methodIdx; //方法列表索引
u4 accessFlags; //访问标志
u4 codeOff; //指向DexCode偏移
};
然后对于native没有dexcode因为不在这里,在native层,如果是普通java方法,如下
struct DexCode{
u2 registersSize; //使用寄存器个数
u2 insSize; //参数个数
u2 outsSize; //调用其他方法使用的寄存器个数
u2 triesSize; //try catch个数,java里的trycatch
u4 debugInfoOff; //调试信息偏移
u4 insnsSize; //指令个数
u2 insns[1]; //指令
// u2 padding //结构体对齐
// try_item[triesSize] //DexTry结构
// uleb128 handlersSize //Try hanlder个数
// catch_handler_item[handlersSize] //DexCatchHanlder
};
做成这种结构,原因是想把这个dex文件压缩,比如有些字符串比如类型反复使用,其实只要一份就好。
Demo
比如打开010editor,观察class
这里classidx指向tpyeid是4,指向字符串表第四项。
注意前面的u4,是一个变长的代表1个或者4个字节的无符号数。算法如下
// get the actual uint value of the uleb128
uint uleb128_value(uleb128 &u) {
local uint result;
local ubyte cur;
result = u.val[0];
if(result > 0x7f) {
cur = u.val[1];
result = (result & 0x7f) | (uint)((cur & 0x7f) << 7);
if(cur > 0x7f) {
cur = u.val[2];
result |= (uint)(cur & 0x7f) << 14;
if(cur > 0x7f) {
cur = u.val[3];
result |= (uint)(cur & 0x7f) << 21;
if(cur > 0x7f) {
cur = u.val[4];
result |= (uint)cur << 28;
}
}
}
}
return result;
}
大致就是看一个字节最高位如果是1,就拼接起来。
再看下函数,比如main函数