dex文件格式

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_idsclass_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打開文件。

dex header

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

MapList偏移

讀取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個字段如下:

DexStringId對象
序號 偏移 字符串
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):

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] */
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容