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 field
typedef struct {
//origin type is uleb128
u4 fieldIdx; /* 指向一个字段表里的index */
u4 accessFlags;
}DexField;
//encoded method
typedef 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