dex文件格式

dex文件格式

dex文件中的数据结构

类型 含义
u1 等同uint8_t,表示1字节的无符号数
u2 等同于uint16_t,表示2字节的无符号数
u4 等同于uint32_t,表示4字节的无符号数
u8 等同于uint64_t,表示8字节的无符号数
sleb128 有符号LEB128,可变长度1~5字节
uleb128 无符号LEB128,可变长度1~5字节
uleb128p1 无符号LEB128值加1,可变长度1~5字节

每个LB125由1~5个字节组成,所有的字节组合在一起表示一个32位的数据。每个字节只有7位有效位,如果第一个字节的最高位为1,表示LEB128需要使用到第2个字节,如果LEB第二个字节的最高位为1,表示会使用到第3个字节,以此类推。LEB128最多使用5个字节,如果读取5个字节后下一个字节最高位仍为1,则表示该dex文件无效。

以字符序列“c0 83 92 25”为例,计算它的uleb128的值:

第1个字节0xc0 > 0x7c ,表示会用到第2个字节。result = 0x0c & 0x7f

第2个字节0x83 > 0x7c,表示会用到第3个字节。result = result + 0x83 & 0x7f << 7

第3个字节0x92 > 0x7c,表示会用到第4个字节。result = result + 0x92 & 0x7c << 14

第4个字节0x25 < 0x7c,表示到了结尾。result = result + 0x25 & 0x7c << 21

计算结果为0x40 + 0x180 + 0x48000 +0x4a00000 = 0x4a481c0

以字符序列“d1 c2 b3 40”为例,计算它的sleb128值。

result = 0xd1 & 0x7f

result=result + 0xc2 & 0x7f << 7

result=result + b3 & 0x7f << 14

result=(result + 40 & 0x7f <<21)<<4)>>4

dex文件整体结构

dex文件由7个部分组成。dex header为dex文件头,它指定了dex文件的一些属性,并记录了其他6部分数据结构在dex文件中的物理偏移。string_idsclass_def 结构可以裂解为索引结构区,真实的数据存放在data中。最后的link_data为静态链接数据区,对于目前生成的dex文件而言,它始终为空。

dex head
string_ids
type_ids
proto_ids
field_ids
method_ids
class_def
data
link_data

未经过优化的dex文件结构表示如下:

struct DexFile {
    DexHeader     header;
    DexStringId     StringIds[StringIdsSize];
    DexTypeId      TypeIds[typeIdsSize];
    DexProtoId     ProtoIds[protoIdsSize];
    DexFieldId      FieldIds[fieldIdsSize];
    DexMethodId   MethodIds[methodIdsSize];
    DexClassDef    Data[];
    DexLink        LinkData;
}

DexFile结构的声明在Android系统源码dalvik\libdex\DexFile.h文件中。

dex文件头

字段名称 偏移量字节 长度(byte) 字段描述
magic[8] 0x0 0x8 dex版本标识
checksum 0x8 0x4 alder32算法, 去除了magic和checksum 字段之外的所有内容的校验码
signature 0xc 0x14 sha-1签名, 去除了magic、checksum和 signature字段之外的所有内容的签名
fileSize 0x20 0x4 整个dex的文件大小
headerSize 0x24 0x4 整个dex文件头的大小 (固定大小为0x70)
endianTag 0x28 0x4 字节序 (大尾方式、小尾方式) 默认为小尾方式 <--> 0x12345678
linkSize 0x2c 0x4 链接段的大小, 默认为0表示静态链接
linkOff 0x30 0x4 链接段开始偏移
mapOff 0x34 0x4 DexMapList的文件偏移
stringIdsSize 0x38 0x4 DexStringId个数
stringIdsOff 0x3c 0x4 DexStringId偏移
typeIdsSize 0x40 0x4 DexTypeId个数
typeIdsOff 0x44 0x4 DexTypeId偏移
protoIdsSize 0x48 0x4 DexProtoId个数
protoIdsOff 0x4c 0x4 DexProtoId偏移
fieldIdsSize 0x50 0x4 DexFieldId个数
fieldIdsOff 0x54 0x4 DexFieldId偏移
methodIdsSize 0x58 0x4 DexMethodId个数
methodIdsOff 0x5c 0x4 DexMethodId偏移
classDefsSize 0x60 0x4 DexClassDef个数
classDefsOff 0x64 0x4 DexClassDef偏移
dataSize 0x68 0x4 数据段大小
dataOff 0x6c 0x4 数据段偏移

DexHeader结构下面的数据为“索引结构区”与“数据区”。

“索引结构区”中各数据结构的偏移地址都是从DexHeader结构的stringIdsOff~classDefsOff字段的值指定的,它们并非真正的类数据,而是指向dex文件的data数据区的偏移或数据结构索引。

DexHeader结构声明如下:

struct DexHeader {
    u1  magic[8];           
    u4  checksum;          
    u1  signature[kSHA1DigestLen];
    u4  fileSize;           
    u4  headerSize;        
    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;
};

dex文件结构分析

DexMapItem

Dalvik虚拟机解析dex文件的内容,最终将其映射成DexMapList数据结构,DexHeader结构的mapOff字段指明了DexMapList结构在dex文件中的偏移,它的声明如下。

struct DexMapList {
    u4  size;               /* DexMapItem的个数 */
    DexMapItem list[1];     /* DexMapItem的结构 */
};

size字段表示接下来有多少个DexMapItem结构,DexMapItem的结构声明如下。

struct DexMapItem {
    u2 type;              /* kDexType开头的类型 */
    u2 unused;            /*未使用,用于字节对齐 */
    u4 size;              /* 指定类型的个数 */
    u4 offset;            /* 指定类型的文件偏移 */
};

type字段为一个枚举常量,如下所示,通过类型名称很容易判断它的具体类型。

enum {
    kDexTypeHeaderItem               = 0x0000,
    kDexTypeStringIdItem             = 0x0001,
    kDexTypeTypeIdItem               = 0x0002,
    kDexTypeProtoIdItem              = 0x0003,
    kDexTypeFieldIdItem              = 0x0004,
    kDexTypeMethodIdItem             = 0x0005,
    kDexTypeClassDefItem             = 0x0006,
    kDexTypeMapList                  = 0x1000,
    kDexTypeTypeList                 = 0x1001,
    kDexTypeAnnotationSetRefList     = 0x1002,
    kDexTypeAnnotationSetItem        = 0x1003,
    kDexTypeClassDataItem            = 0x2000,
    kDexTypeCodeItem                 = 0x2001,
    kDexTypeStringDataItem           = 0x2002,
    kDexTypeDebugInfoItem            = 0x2003,
    kDexTypeAnnotationItem           = 0x2004,
    kDexTypeEncodedArrayItem         = 0x2005,
    kDexTypeAnnotationsDirectoryItem = 0x2006,
};

以hello.dex为例,用winhex打开文件。

dex文件格式_第1张图片
dex header

在偏移0x34处找到mapOff,即MapList的偏移:0x0290

dex文件格式_第2张图片
MapList偏移

读取0x290处的一个双字值为0x0d,表明接下来会有13个DexMapItem。(此处占四个字节?)

根据前文中提到的DexMapItem结构,一个DexMapItem占12个字节,1-2个字节表示类别,5-8个字节表示个数,9-12个字节表示偏移量。

所有DexMapItem如下表:

类型 个数 偏移
kDexTypeHeaderItem 0x1 0x0
kDexTypeStringIdItem 0x10 0x70
kDexTypeTypeIdItem 0x7 0xb0
kDexTypeProtoIdItem 0x4 0xcc
kDexTypeFieldIdItem 0x1 0xfc
kDexTypeMethodIdItem 0x5 0x104
kDexTypeClassDefItem 0x1 0x12c
kDexTypeCodeItem 0x3 0x1b4
kDexTypeTypeList 0x3 0x1ca
kDexTypeStringDataItem 0x10 0x16c
kDexTypeDebugInfoItem 0x3 0x267
kDexTypeClassDataItem 0x1 0x27b
kDexTypeMapList 0x1 0x290

kDexTypeHeaderItem描述了DexHeader部分,它占用了文件0x70个字节的空间。而接下来的kDexTypeStringIdItem~kDexTypeClassDataItem与DexHeader当中对应的类型个数字段的值是相同的。

kDexTypeStringIdItem

比如kDexTypeStringIdItem对应了DexHeader的stringIdsSize与stringIdsOff字段,表明了在0

x70偏移处,有0x10个DexStringId对象。DexStringId结构的声明如下。

struct DexStringId {
    u4 stringDataOff;      /* 字符串数据偏移 */
};

DexStringId结构只有一个stringDataOff字段,直接指向字符串数据,从0x70开始,有0x10个DexStringId对象,一个对象是4个字节代表字符串偏移。一共16个字段如下:

dex文件格式_第3张图片
DexStringId对象
序号 偏移 字符串
0x0 0x1ca
0x1 0x1d2 Hello.java
0x2 0x1de I
0x3 0x1e1 III
0x4 0x1e6 LHello;
0x5 0x1ef Ljava/io/PrintStream;
0x6 0x206 Ljava/lang/Object;
0x7 0x21a Ljava/lang/System;
0x8 0x22e V
0x9 0x231 VI
0xa 0x235 VL
0xb 0x392 [Ljava/lang/String;
0xc 0x24e foo
0xd 0x253 main
0xe 0x259 out
0xf 0x25e println

上表中的字符串并非普通的ascii字符串,他们是有MUTF-8编码表示的。MUTF-8含义为Modified UTF-8。

kDexTypeTypeIdItem

他对应DexHeader中的typeIdsSize和typeIdsOff字段,指向的结构体为:

struct DexTypeId {
    u4  descriptorIdx;      /* 指向DexStringId列表的索引 */
};

descriptorIdx是指向DexStringId列表的索引 ,他对应的字符串代表具体类的类型,我们根据上面字段可知:从0xb0起有0x7个DexTypeId结构:

DexTypeId结构
类型索引 字符串索引 字符串
0 0x2 I
1 0x4 LHello;
2 0x5 Ljava/io/PrintStream;
3 0x6 Ljava/lang/Object;
4 0x7 Ljava/lang/System;
5 0x8 V
6 0xb [Ljava/lang/String;

kDexTypeProtoIdItem

对应DexHeader中的protoIdsSize与protoIdsOff字段,声明如下:

struct DexProtoId {
    u4  shortyIdx;          /* 指向DexStringId列表的索引 */
    u4  returnTypeIdx;      /* 指向DexTypeId列表的索引 */
    u4  parametersOff;      /* 指向DexTypeList的偏移 */
};

他是一个方法的声明结构体,shortyIdx为方法声明字符串,returnTypeIdx为方法返回类型字符串,parametersOff指向一个DexTypeList的结构体存放了方法的参数列表 。DexTypeList声明如下:

struct  DexTypeList{
    u4  size;               /* 接下来DexTypeItem的个数 */
    DexTypeItem list[1];    /* DexTypeItem结构 */
};

DexTypeItem声明如下:

struct DexTypeItem {
    u2  typeIdx;            /* 指向DexTypeId列表的索引 */
};

从0xcc开始,有4个DexProtoId,如表:

DexProtoId结构表

索引 方法声明 返回类型 参数列表
0 III I 2个参数II
1 V V 无参数
2 VI V 1个参数I
3 VL V 1个参数[Ljava/lang/String;

通过上表可以发现,方法声明由返回类型与参数列表组成,并且返回类型位于参数列表的前面。

kDexTypeFieldIdItem

对应DexHeader中的fieldIdsSize和fieldIdsOff字段,指向DexFieldId结构体 :

struct DexFieldId {
    u2  classIdx;           /* 类的类型,指向DexTypeId列表索引 */
    u2  typeIdx;            /* 字段类型,指向DexTypeId列表索引 */
    u4  nameIdx;            /* 字段名,指向DexStringId列表 */
};

DexFieldId结构体中的数据全部是索引值,指明了字段所在的类,字段的类型以及字段名,从0xfc开始共有1个DexFieldId结构。结果如图:

类类型 字段类型 字段名
Ljava/lang/System; Ljava/io/PrintStream; out

kDexTypeMethodIdItem

它对应DexHeader中的methodIdsSize与methodIdsOff字段,指向的结构体DexMethodId

struct DexMethodId {
    u2  classIdx;           /* 类的类型,指向DexTypeId列表的索引 */
    u2  protoIdx;           /* 声明类型,指向DexProtoId列表索引 */
    u4  nameIdx;            /* 方法名,指向DexStringId列表的索引 */
};

数据也是索引,指明了方法所在的类,方法声明和方法名。从0x104有0x5个kDexTypeMethodIdItem

类类型 方法声明 方法名
LHello V
LHello III foo
LHello VL main
Ljava/io/PrintStream; VI println
Ljava/lang/Object; V

kDexTypeClassDefItem

对应DexHeader中的classDefsSize和classDefsOff字段,指向结构体DexClassDef

struct DexClassDef {
    u4  classIdx;           /* 类的类型,指向DexTypeId列表索引 */
    u4  accessFlags;        /* 访问标志 */
    u4  superclassIdx;      /* 父类的类型,指向DexTypeId列表的索引 */
    u4  interfacesOff;      /* 实现了哪些接口,指向DexTypeList结构的偏移 */
    u4  sourceFileIdx;      /* 源文件名,指向DexStringId列表的索引 */
    u4  annotationsOff;     /* 注解,指向DexAnnotationsDirectoryItem结构的偏移 */
    u4  classDataOff;       /* 指向DexClassData结构的偏移 */
    u4  staticValuesOff;    /* 指向DexEncodedArray结构的偏移 */
};

  • classIdx:索引值,表明类的类型
  • accessFlags:类的访问标志,它是以ACC_开头的一个枚举值
  • superclassIdx:父类类型索引值,
  • interfacesOff:如果类中含有接口声明或实现,字段会指向1个DexTypeList结构,否则这里为0
  • sourceFileIdx :字符串索引值,表示类所在的源文件名称
  • annotationsOff :指向注解目录结构
  • classDataOff :指向DexClassData结构,这是数据部分
  • staticValuesOff :指向DexEncodedArray结构,记录了类中的静态数据

DexClassData

struct DexClassData {
    DexClassDataHeader header; //指向DexClassDataHeader,字段和方法个数
    DexField*          staticFields; //静态字段
    DexField*          instanceFields; //实例字段
    DexMethod*         directMethods; //直接方法
    DexMethod*         virtualMethods;//虚方法
};

DexClassDataHeader

记录了当前类中的字段和方法的数目,声明如下:

struct DexClassDataHeader {
    u4 staticFieldsSize; //静态字段个数
    u4 instanceFieldsSize;//实例字段个数
    u4 directMethodsSize;//直接方法个数
    u4 virtualMethodsSize;//虚方法个数
};

DexField

描述了字段类型与访问标志,声明如下:

struct DexField {
    u4 fieldIdx;    /* 指向DexFieldId列表的索引 */
    u4 accessFlags; /* 访问标志 */
};

DexMethod

struct DexMethod {
    u4 methodIdx;    /* 指向DexMethodId列表的索引 */
    u4 accessFlags;  /* 访问标志 */
    u4 codeOff;      /* 指向DexCode结构的偏移 */
};

codeOff字段指向一个DexCode结构体,声明如下:

/dalvik/libdex/DexFile.h

struct DexCode {
    u2  registersSize;//使用寄存器个数
    u2  insSize;//参数个数
    u2  outsSize;//调用其他方法时使用的寄存器个数
    u2  triesSize;//try/catch个数
    u4  debugInfoOff;//指向调试信息的偏移
    u4  insnsSize;//指令集个数,以2字节为单位
    u2  insns[1];//指令集
    /* followed by optional u2 padding */
    /* followed by try_item[triesSize] */
    /* followed by uleb handlersSize */
    /* followed by catch_handler_item[handlersSize] */
};

你可能感兴趣的:(dex文件格式)