Dex文件结构
文件头
typedef struct { u1 magic[MAGIC_LENGTH]; /* includes version number */ u4 checksum; /* adler32 校验剩余长度的文件 */ u1 signature[kSHA1DigestLen]; /* SHA-1 文件签名 */ u4 fileSize; /* length of entire file */ u4 headerSize; /* offset to start of next section */ u4 endianTag; u4 linkSize; u4 linkOff; u4 mapOff; 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;}DexHeader;
DexHeader由于是定长结构 直接格式化就好
Leb128编码
每个LEB128由1到5个字节组成,所有字节组合到一起代表一个32位值。除了最后一个字节的最高标志位为0,
其它的为1.剩下的7位为有效负荷,第二个字节的7位接上。有符号LEB128的符号由最后字节的有效负荷最高位决定。
例如:0x7f80
01111111 10000000
按无符号leb128解析 0x3f80
按有符号leb128解析 -128 (注意先转补码)
具体解析算法在示例代码中
字符串表
字符串表包含了dex文件/代码中使用到的字符串
字符串表存放的是StringId,具体字符串值在数据段data中
typedef struct { u4 stringDataOff; /* string_data_item 偏移 */}DexStringId;struct string_data_item { u2 uleb128; //字符串长度 u1 str[1]; //字符串内容}
string_data_item 起始2字节是uleb128编码,解码后可得到字符串的长度
类型表
typedef struct { u4 descriptorIdx; /* 指向一个string_id的index */}DexTypeId;
字段表
typedef struct { u2 classIdx; /* index into typeIds list for defining class */ u2 typeIdx; /* index into typeIds for field type */ u4 nameIdx; /* index into stringIds for field name */}DexFieldId;
Field描述的是一个类中的成员变量/静态变量
原型表
typedef struct { u4 shortyIdx; /* index into stringIds for shorty descriptor */ u4 returnTypeIdx; /* index into typeIds list for return type */ u4 parametersOff; /* file offset to type_list for parameter types */}DexProtoId;
Proto原型描述的是一个函数的返回类型 参数类型列表
由于参数可能是多个 parametersOff指向的是一个 type_list结构
typedef struct { u2 typeIdx; /* index into typeIds */}DexTypeItem;typedef struct { u4 size; /* #of entries in list */ DexTypeItem list[1]; /* entries */}DexTypeList;
如果parametersOff为0 表示该函数没有参数
函数表
typedef struct { u2 classIdx; /* index into typeIds list for defining class */ u2 protoIdx; /* index into protoIds for method prototype */ u4 nameIdx; /* index into stringIds for method name */}DexMethodId;
Method描述的是函数所在类 原型 名称
类数据
typedef struct{ u4 classIdx; /* index into typeIds for this class */ u4 accessFlags; u4 superclassIdx; /* index into typeIds for superclass */ u4 interfacesOff; /* file offset to DexTypeList */ u4 sourceFileIdx; /* index into stringIds for source file name */ u4 annotationsOff; /* file offset to annotations_directory_item */ u4 classDataOff; /* file offset to class_data_item */ u4 staticValuesOff; /* file offset to DexEncodedArray */}DexClassDef;
superclassIdx 为0表示父类是 java/lang/Object
interfacesOff/annotationsOff/classDataOff/staticValuesOff 都由可能是0 表示类中没有该类型的数据,例如一个标记类 可能classDataOff就会为0 因为没有定义任何函数/字段
sourceFileIdx 可能会是一个无效的id
#define kDexNoIndex 0xffffffff /* not a valid index value */
classDataOff 表示类数据的偏移 指向的是class_data结构
struct class_data{ u4_uleb128 staticFieldsSize; u4_uleb128 instanceFieldsSize; u4_uleb128 directMethodsSize; u4_uleb128 virtualMethodsSize; DexField staticFields[staticFieldsSize]; DexField instanceFields[instanceFieldsSize]; DexMethod directMethods[directMethodsSize]; DexMethod virtualMethods[virtualMethodsSize];}//encoded fieldtypedef struct { //origin type is uleb128 u4 fieldIdx; /* 指向一个字段表里的index */ u4 accessFlags;}DexField;//encoded methodtypedef struct{ //origin type is uleb128 u4 methodIdx; /* 指向一个函数表里的index */ u4 accessFlags; u4 codeOff; /* DexCode 偏移*/}DexMethod;typedef struct { u2 registersSize; //代码块内使用到的寄存器个数 u2 insSize; //入参字数 u2 outsSize; //出参字数 u2 triesSize; //try_catch个数 u4 debugInfoOff; /* file offset to debug info stream */ u4 insnsSize; /*字节码数目*/ u2 insns[1]; //字节码内容 //下面的内容都是当 triesSize>0的时候才会出现 //padding 使try-handler-table 和 字节码之间 四字节对齐 /* followed by optional u2 padding */ //try_cat处理表内容 这里实现的是class文件中的try-handler-table /* followed by try_item[triesSize] */ /* followed by uleb128 handlersSize */ /* followed by catch_handler_item[handlersSize] */}DexCode;
dex字节码的翻译和class字节码翻译差不多,对着规范翻译就好
综述
android vm 采用dex字节码而不是class字节码的优势?
- dex文件由多个class文件合并而来,把多个常量池合并到一个常量池,避免了常量冗余,有利于运行时的常量内存共享
- 加载一个dex可以加载多个相互依赖的class,减少了文件io
- arm cpu具有较多的通用寄存器,vm设计基于寄存器的执行流程,会加速函数的传参和执行
本文代码
DexParserDemo
参考文档
Bytecode for the Dalvik VM