dex文件格式
dex文件中的數(shù)據(jù)結(jié)構(gòu)
| 類型 | 含義 |
|---|---|
| u1 | 等同uint8_t,表示1字節(jié)的無符號數(shù) |
| u2 | 等同于uint16_t,表示2字節(jié)的無符號數(shù) |
| u4 | 等同于uint32_t,表示4字節(jié)的無符號數(shù) |
| u8 | 等同于uint64_t,表示8字節(jié)的無符號數(shù) |
| sleb128 | 有符號LEB128,可變長度1~5字節(jié) |
| uleb128 | 無符號LEB128,可變長度1~5字節(jié) |
| uleb128p1 | 無符號LEB128值加1,可變長度1~5字節(jié) |
每個LB125由1~5個字節(jié)組成,所有的字節(jié)組合在一起表示一個32位的數(shù)據(jù)。每個字節(jié)只有7位有效位,如果第一個字節(jié)的最高位為1,表示LEB128需要使用到第2個字節(jié),如果LEB第二個字節(jié)的最高位為1,表示會使用到第3個字節(jié),以此類推。LEB128最多使用5個字節(jié),如果讀取5個字節(jié)后下一個字節(jié)最高位仍為1,則表示該dex文件無效。
以字符序列“c0 83 92 25”為例,計算它的uleb128的值:
第1個字節(jié)0xc0 > 0x7c ,表示會用到第2個字節(jié)。result = 0x0c & 0x7f
第2個字節(jié)0x83 > 0x7c,表示會用到第3個字節(jié)。result = result + 0x83 & 0x7f << 7
第3個字節(jié)0x92 > 0x7c,表示會用到第4個字節(jié)。result = result + 0x92 & 0x7c << 14
第4個字節(jié)0x25 < 0x7c,表示到了結(jié)尾。result = result + 0x25 & 0x7c << 21
計算結(jié)果為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文件整體結(jié)構(gòu)
dex文件由7個部分組成。dex header為dex文件頭,它指定了dex文件的一些屬性,并記錄了其他6部分?jǐn)?shù)據(jù)結(jié)構(gòu)在dex文件中的物理偏移。string_ids和 class_def 結(jié)構(gòu)可以裂解為索引結(jié)構(gòu)區(qū),真實(shí)的數(shù)據(jù)存放在data中。最后的link_data為靜態(tài)鏈接數(shù)據(jù)區(qū),對于目前生成的dex文件而言,它始終為空。
| dex head |
|---|
| string_ids |
| type_ids |
| proto_ids |
| field_ids |
| method_ids |
| class_def |
| data |
| link_data |
未經(jīng)過優(yōu)化的dex文件結(jié)構(gòu)表示如下:
struct DexFile {
DexHeader header;
DexStringId StringIds[StringIdsSize];
DexTypeId TypeIds[typeIdsSize];
DexProtoId ProtoIds[protoIdsSize];
DexFieldId FieldIds[fieldIdsSize];
DexMethodId MethodIds[methodIdsSize];
DexClassDef Data[];
DexLink LinkData;
}
DexFile結(jié)構(gòu)的聲明在Android系統(tǒng)源碼dalvik\libdex\DexFile.h文件中。
dex文件頭
| 字段名稱 | 偏移量字節(jié) | 長度(byte) | 字段描述 |
|---|---|---|---|
| magic[8] | 0x0 | 0x8 | dex版本標(biāo)識 |
| checksum | 0x8 | 0x4 | alder32算法, 去除了magic和checksum 字段之外的所有內(nèi)容的校驗(yàn)碼 |
| signature | 0xc | 0x14 | sha-1簽名, 去除了magic、checksum和 signature字段之外的所有內(nèi)容的簽名 |
| fileSize | 0x20 | 0x4 | 整個dex的文件大小 |
| headerSize | 0x24 | 0x4 | 整個dex文件頭的大小 (固定大小為0x70) |
| endianTag | 0x28 | 0x4 | 字節(jié)序 (大尾方式、小尾方式) 默認(rèn)為小尾方式 <--> 0x12345678 |
| linkSize | 0x2c | 0x4 | 鏈接段的大小, 默認(rèn)為0表示靜態(tài)鏈接 |
| linkOff | 0x30 | 0x4 | 鏈接段開始偏移 |
| mapOff | 0x34 | 0x4 | DexMapList的文件偏移 |
| stringIdsSize | 0x38 | 0x4 | DexStringId個數(shù) |
| stringIdsOff | 0x3c | 0x4 | DexStringId偏移 |
| typeIdsSize | 0x40 | 0x4 | DexTypeId個數(shù) |
| typeIdsOff | 0x44 | 0x4 | DexTypeId偏移 |
| protoIdsSize | 0x48 | 0x4 | DexProtoId個數(shù) |
| protoIdsOff | 0x4c | 0x4 | DexProtoId偏移 |
| fieldIdsSize | 0x50 | 0x4 | DexFieldId個數(shù) |
| fieldIdsOff | 0x54 | 0x4 | DexFieldId偏移 |
| methodIdsSize | 0x58 | 0x4 | DexMethodId個數(shù) |
| methodIdsOff | 0x5c | 0x4 | DexMethodId偏移 |
| classDefsSize | 0x60 | 0x4 | DexClassDef個數(shù) |
| classDefsOff | 0x64 | 0x4 | DexClassDef偏移 |
| dataSize | 0x68 | 0x4 | 數(shù)據(jù)段大小 |
| dataOff | 0x6c | 0x4 | 數(shù)據(jù)段偏移 |
DexHeader結(jié)構(gòu)下面的數(shù)據(jù)為“索引結(jié)構(gòu)區(qū)”與“數(shù)據(jù)區(qū)”。
“索引結(jié)構(gòu)區(qū)”中各數(shù)據(jù)結(jié)構(gòu)的偏移地址都是從DexHeader結(jié)構(gòu)的stringIdsOff~classDefsOff字段的值指定的,它們并非真正的類數(shù)據(jù),而是指向dex文件的data數(shù)據(jù)區(qū)的偏移或數(shù)據(jù)結(jié)構(gòu)索引。
DexHeader結(jié)構(gòu)聲明如下:
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文件結(jié)構(gòu)分析
DexMapItem
Dalvik虛擬機(jī)解析dex文件的內(nèi)容,最終將其映射成DexMapList數(shù)據(jù)結(jié)構(gòu),DexHeader結(jié)構(gòu)的mapOff字段指明了DexMapList結(jié)構(gòu)在dex文件中的偏移,它的聲明如下。
struct DexMapList {
u4 size; /* DexMapItem的個數(shù) */
DexMapItem list[1]; /* DexMapItem的結(jié)構(gòu) */
};
size字段表示接下來有多少個DexMapItem結(jié)構(gòu),DexMapItem的結(jié)構(gòu)聲明如下。
struct DexMapItem {
u2 type; /* kDexType開頭的類型 */
u2 unused; /*未使用,用于字節(jié)對齊 */
u4 size; /* 指定類型的個數(shù) */
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打開文件。

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

讀取0x290處的一個雙字值為0x0d,表明接下來會有13個DexMapItem。(此處占四個字節(jié)?)
根據(jù)前文中提到的DexMapItem結(jié)構(gòu),一個DexMapItem占12個字節(jié),1-2個字節(jié)表示類別,5-8個字節(jié)表示個數(shù),9-12個字節(jié)表示偏移量。
所有DexMapItem如下表:
| 類型 | 個數(shù) | 偏移 |
|---|---|---|
| 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個字節(jié)的空間。而接下來的kDexTypeStringIdItem~kDexTypeClassDataItem與DexHeader當(dāng)中對應(yīng)的類型個數(shù)字段的值是相同的。
kDexTypeStringIdItem
比如kDexTypeStringIdItem對應(yīng)了DexHeader的stringIdsSize與stringIdsOff字段,表明了在0
x70偏移處,有0x10個DexStringId對象。DexStringId結(jié)構(gòu)的聲明如下。
struct DexStringId {
u4 stringDataOff; /* 字符串?dāng)?shù)據(jù)偏移 */
};
DexStringId結(jié)構(gòu)只有一個stringDataOff字段,直接指向字符串?dāng)?shù)據(jù),從0x70開始,有0x10個DexStringId對象,一個對象是4個字節(jié)代表字符串偏移。一共16個字段如下:

| 序號 | 偏移 | 字符串 |
|---|---|---|
| 0x0 | 0x1ca | <init> |
| 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
他對應(yīng)DexHeader中的typeIdsSize和typeIdsOff字段,指向的結(jié)構(gòu)體為:
struct DexTypeId {
u4 descriptorIdx; /* 指向DexStringId列表的索引 */
};
descriptorIdx是指向DexStringId列表的索引 ,他對應(yīng)的字符串代表具體類的類型,我們根據(jù)上面字段可知:從0xb0起有0x7個DexTypeId結(jié)構(gòu):

| 類型索引 | 字符串索引 | 字符串 |
|---|---|---|
| 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
對應(yīng)DexHeader中的protoIdsSize與protoIdsOff字段,聲明如下:
struct DexProtoId {
u4 shortyIdx; /* 指向DexStringId列表的索引 */
u4 returnTypeIdx; /* 指向DexTypeId列表的索引 */
u4 parametersOff; /* 指向DexTypeList的偏移 */
};
他是一個方法的聲明結(jié)構(gòu)體,shortyIdx為方法聲明字符串,returnTypeIdx為方法返回類型字符串,parametersOff指向一個DexTypeList的結(jié)構(gòu)體存放了方法的參數(shù)列表 。DexTypeList聲明如下:
struct DexTypeList{
u4 size; /* 接下來DexTypeItem的個數(shù) */
DexTypeItem list[1]; /* DexTypeItem結(jié)構(gòu) */
};
DexTypeItem聲明如下:
struct DexTypeItem {
u2 typeIdx; /* 指向DexTypeId列表的索引 */
};
從0xcc開始,有4個DexProtoId,如表:
DexProtoId結(jié)構(gòu)表
| 索引 | 方法聲明 | 返回類型 | 參數(shù)列表 |
|---|---|---|---|
| 0 | III |
I |
2個參數(shù)I、I
|
| 1 | V |
V |
無參數(shù) |
| 2 | VI |
V |
1個參數(shù)I
|
| 3 | VL |
V |
1個參數(shù)[Ljava/lang/String;
|
通過上表可以發(fā)現(xiàn),方法聲明由返回類型與參數(shù)列表組成,并且返回類型位于參數(shù)列表的前面。
kDexTypeFieldIdItem
對應(yīng)DexHeader中的fieldIdsSize和fieldIdsOff字段,指向DexFieldId結(jié)構(gòu)體 :
struct DexFieldId {
u2 classIdx; /* 類的類型,指向DexTypeId列表索引 */
u2 typeIdx; /* 字段類型,指向DexTypeId列表索引 */
u4 nameIdx; /* 字段名,指向DexStringId列表 */
};
DexFieldId結(jié)構(gòu)體中的數(shù)據(jù)全部是索引值,指明了字段所在的類,字段的類型以及字段名,從0xfc開始共有1個DexFieldId結(jié)構(gòu)。結(jié)果如圖:
| 類類型 | 字段類型 | 字段名 |
|---|---|---|
Ljava/lang/System; |
Ljava/io/PrintStream; |
out |
kDexTypeMethodIdItem
它對應(yīng)DexHeader中的methodIdsSize與methodIdsOff字段,指向的結(jié)構(gòu)體DexMethodId
struct DexMethodId {
u2 classIdx; /* 類的類型,指向DexTypeId列表的索引 */
u2 protoIdx; /* 聲明類型,指向DexProtoId列表索引 */
u4 nameIdx; /* 方法名,指向DexStringId列表的索引 */
};
數(shù)據(jù)也是索引,指明了方法所在的類,方法聲明和方法名。從0x104有0x5個kDexTypeMethodIdItem
| 類類型 | 方法聲明 | 方法名 |
|---|---|---|
LHello |
V |
<init> |
LHello |
III |
foo |
LHello |
VL |
main |
Ljava/io/PrintStream; |
VI |
println |
Ljava/lang/Object; |
V |
<init> |
kDexTypeClassDefItem
對應(yīng)DexHeader中的classDefsSize和classDefsOff字段,指向結(jié)構(gòu)體DexClassDef
struct DexClassDef {
u4 classIdx; /* 類的類型,指向DexTypeId列表索引 */
u4 accessFlags; /* 訪問標(biāo)志 */
u4 superclassIdx; /* 父類的類型,指向DexTypeId列表的索引 */
u4 interfacesOff; /* 實(shí)現(xiàn)了哪些接口,指向DexTypeList結(jié)構(gòu)的偏移 */
u4 sourceFileIdx; /* 源文件名,指向DexStringId列表的索引 */
u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem結(jié)構(gòu)的偏移 */
u4 classDataOff; /* 指向DexClassData結(jié)構(gòu)的偏移 */
u4 staticValuesOff; /* 指向DexEncodedArray結(jié)構(gòu)的偏移 */
};
-
classIdx:索引值,表明類的類型 -
accessFlags:類的訪問標(biāo)志,它是以ACC_開頭的一個枚舉值 -
superclassIdx:父類類型索引值, -
interfacesOff:如果類中含有接口聲明或?qū)崿F(xiàn),字段會指向1個DexTypeList結(jié)構(gòu),否則這里為0 -
sourceFileIdx:字符串索引值,表示類所在的源文件名稱 -
annotationsOff:指向注解目錄結(jié)構(gòu) -
classDataOff:指向DexClassData結(jié)構(gòu),這是數(shù)據(jù)部分 -
staticValuesOff:指向DexEncodedArray結(jié)構(gòu),記錄了類中的靜態(tài)數(shù)據(jù)
DexClassData
struct DexClassData {
DexClassDataHeader header; //指向DexClassDataHeader,字段和方法個數(shù)
DexField* staticFields; //靜態(tài)字段
DexField* instanceFields; //實(shí)例字段
DexMethod* directMethods; //直接方法
DexMethod* virtualMethods;//虛方法
};
DexClassDataHeader
記錄了當(dāng)前類中的字段和方法的數(shù)目,聲明如下:
struct DexClassDataHeader {
u4 staticFieldsSize; //靜態(tài)字段個數(shù)
u4 instanceFieldsSize;//實(shí)例字段個數(shù)
u4 directMethodsSize;//直接方法個數(shù)
u4 virtualMethodsSize;//虛方法個數(shù)
};
DexField
描述了字段類型與訪問標(biāo)志,聲明如下:
struct DexField {
u4 fieldIdx; /* 指向DexFieldId列表的索引 */
u4 accessFlags; /* 訪問標(biāo)志 */
};
DexMethod
struct DexMethod {
u4 methodIdx; /* 指向DexMethodId列表的索引 */
u4 accessFlags; /* 訪問標(biāo)志 */
u4 codeOff; /* 指向DexCode結(jié)構(gòu)的偏移 */
};
codeOff字段指向一個DexCode結(jié)構(gòu)體,聲明如下:
/dalvik/libdex/DexFile.h
struct DexCode {
u2 registersSize;//使用寄存器個數(shù)
u2 insSize;//參數(shù)個數(shù)
u2 outsSize;//調(diào)用其他方法時使用的寄存器個數(shù)
u2 triesSize;//try/catch個數(shù)
u4 debugInfoOff;//指向調(diào)試信息的偏移
u4 insnsSize;//指令集個數(shù),以2字節(jié)為單位
u2 insns[1];//指令集
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb handlersSize */
/* followed by catch_handler_item[handlersSize] */
};