Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Dex文件結構解析

Android Dex文件結構解析

編輯:關於Android編程

Java源文件通過Java編譯器生成CLASS文件,再通過dx工具轉換為classes.dex文件。
DEX文件從整體上來看是一個索引的結構,類名、方法名、字段名等信息都存儲在常量池中,這樣能夠充分減少存儲空間,相較於Java字節碼文件更適合手機設備。

DEX文件的相關結構聲明定義在/dalvik/libdex/DexFile.h文件中,下面我們先來看一下DEX文件中使用到的數據結構。

表1 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字節

DEX文件的基本結構如下圖所示:

header string_ids type_ids proto_ids field_ids method_ids class_def data link_data
圖1 DEX文件結構

1. header(基本信息)

header是DEX文件頭,包含magic字段、alder32校驗值、SHA-1哈希值、string_ids的個數以及偏移地址等。DEX文件的頭結構很固定,占用0x70個字節,具體定義代碼如下所示(摘自DexFile.h):

/*
 * Direct-mapped "header_item" struct.
 */


struct DexHeader {
    u1  magic[8];           /* includes version number */  
    u4  checksum;           /* adler32 checksum */  
    u1  signature[kSHA1DigestLen]; /* SHA-1 hash */
    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;
};

magic[8]:共8個字節。目前為固定值dex\n035。

checksum:文件校驗碼,使用alder32算法校驗文件除去magic、checksum外余下的所有文件區域,用於檢查文件錯誤。

signature:使用 SHA-1算法hash除去magic,checksum和signature外余下的所有文件區域 ,用於唯一識別本文件 。

fileSize:DEX文件的長度。

headerSize:header大小,一般固定為0x70字節。

endianTag:指定了DEX運行環境的cpu字節序,預設值ENDIAN_CONSTANT等於0x12345678,表示默認采用Little-Endian字節序。


linkSizelinkOff:指定鏈接段的大小與文件偏移,大多數情況下它們的值都為0。link_size:LinkSection大小,如果為0則表示該DEX文件不是靜態鏈接。link_off用來表示LinkSection距離DEX頭的偏移地址,如果LinkSize為0,此值也會為0。

mapOff:DexMapList結構的文件偏移。

stringIdsSizestringIdsOff:DexStringId結構的數據段大小與文件偏移。

typeIdsSizetypeIdsOff:DexTypeId結構的數據段大小與文件偏移。

protoIdsSizeprotoIdsSize:DexProtoId結構的數據段大小與文件偏移。

fieldIdsSizefieldIdsSize:DexFieldId結構的數據段大小與文件偏移。

methodIdsSizemethodIdsSize:DexMethodId結構的數據段大小與文件偏移。

classDefsSizeclassDefsOff:DexClassDef結構的數據段大小與文件偏移。

dataSizedataOff:數據段的大小與文件偏移。


下面我們來看某apk中classes.dex的解析結果,確實與上面的結構一致:

image

2.DexMapList區段(大綱)

Dalvik虛擬機解析DEX文件的內容,最終將其映射成DexMapList數據結構,它實際上包含所有其他區段的結構大綱。DexHeader中的mapOff字段指明了DexMapList結構在DEX文件中的偏移。具體定義代碼如下所示:

struct DexMapList {
    u4 size;               /* DexMapItem的個數 */
    DexMapItem list[1];    /* DexMapItem的結構 */
};

struct DexMapItem {   
    u2 type;      /* kDexType開頭的類型 */
    u2 unused;  /* 未使用,用於字節對齊 */
    u4 size;    /* type指定類型的個數,它們在dex文件中連續存放 */
    u4 offset;  /* 指定類型數據的文件偏移 */
};

/* type字段為一個枚舉常量,通過類型名稱很容易判斷它的具體類型。 */
/* map item type codes */
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,
};

下面我們來看一下010Editor對某classes.dex文件的解析出的DexMapList結構。上面DexMapList結構中的size字段表示list數組的成員個數,即DexMapItem結構的數量:圖中是11h,表示共有17個DexMapItem結構,與圖中的list數組大小相符。

image

然後我們再來看下DexMapItem的結構。例如對於下圖中的DexMapItem的第一項來說,type等於0說明其是kDexTypeHeaderItem類型的結構;unused一般都為0;size為1代表該結構僅有一個,即只有一個Dex文件頭;offset為0代表Dex文件頭從0h開始。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="image" src="/uploadfile/Collfiles/20160627/20160627092154831.png" title="\" />

最後我們將所有DexMapItem結構整理成下表:

類型(type) 個數(size) 偏移(offset) kDexTypeHeaderItem(0x0000) 0x1 0x0 kDexTypeStringIdItem(0x0001) 0xA115 0x70 kDexTypeTypeIdItem(0x0002) 0x1D38 0x284C4 kDexTypeProtoIdItem(0x0003) 0x2505 0x2F9A4 kDexTypeFieldIdItem(0x0004) 0x9FB9 0x4B5E0 kDexTypeMethodIdItem(0x0005) 0xC344 0x9B3A8 kDexTypeClassDefItem(0x0006) 0x189D 0xFCDC8 kDexTypeAnnotationSetItem(0x1003) 0x10E0 0x12E168 kDexTypeCodeItem(0x2001) 0x96DB 0x138A34 kDexTypeAnnotationsDirectoryItem(0x2006) 0xCE6 0x4A3EEC kDexTypeTypeList(0x1001) 0x1620 0x4B9894 kDexTypeStringDataItem(0x2002) 0xA115 0x4C74CA kDexTypeDebugInfoItem(0x2003) 0x8FCC 0x5C8544 kDexTypeAnnotationItem(0x2004) 0x101C 0x63FBC1 kDexTypeEncodedArrayItem(0x2005) 0x10E 0x653536 kDexTypeClassDataItem(0x2000) 0x184F 0x65B97A kDexTypeMapList(0x1000) 0x1 0x6B8828

可以看出,其中區段的offset與header中的off是完全相等的。


3.DexStringId區段(字符串)

struct DexStringId {
    u4 stringDataOff;   /* 字符串數據偏移 */
}

DexStringId結構只有一個stringDataOff字段,直接指向字符串數據。這個區段中包含了DEX文件中用到的所有字符串。

4.DexTypeId區段(類名/類型名稱字符串)

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

descriptorIdx為指向DexStringId列表的索引,它對應的字符串代表了具體類的類型(DEX文件中用到的所有基本數據類型和類的名稱)。如下圖中的第一項值為0xAEB,表示其是DexStringId中第0xAEB(2795)項;而第8項值為0x1969,表示其是DexStringId中第0x1969(6505)項。經過我們的驗證,以上分析是正確的。

image

image

image

5.DexProtoId區段(方法聲明=返回類型 + 參數列表)

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

struct DexTypeList {
    u4 size;             /* 接下來DexTypeItem的個數 */
    DexTypeItem list[1]; /* DexTypeItem結構 */
};

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

下面結合實例進行分析:

DexProtoId

shortyIdx:方法聲明字符串,具體而言是由方法的返回類型與參數列表組成的一個字符串,並且返回類型位於參數列表的前面。如“III”“V”“VI”“VL”等。在下圖的三個方法聲明中分別為B、BL、DL。

returnTypeIdx:方法返回類型,指向DexTypeId列表。下圖的分別為byte、byte、double。

parametersOff:指向一個DexTypeList結構體,存放了方法的參數類型。下圖分別為0、0x4BA78C、0x4BA7BC。值為0表示參數為void。

DexTypeList

size:DexTypeItem的個數,即參數的數量。下圖分別為?、1、1,表示後兩個方法都只有一個參數。 list:指向size個DexTypeItem項,每一項代表方法的一個參數。

DexTypeItem

typeIdx:指向DexTypeId列表,最終指向參數類型的字符串。如第三圖61h(97)項在DexTypeId列表中正好指向”Landroid/content/Context;”類型字符串。

image

image

image

image

6.DexFieldId區段(字段)

DexFieldId結構中的數據全部是索引值,指明了字段所在的類、字段的類型以及字段名。

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

如下圖,可以看到字段所屬類名為MTT.ThirdAppInfoNew,字段類型為int,字段名為iCoreType。

image

7.DexMethodId區段(方法)

DexMethodId結構中的數據全部是索引值,指明了方法所在的類、方法的聲明以及方法名。

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

如下圖,可以看到方法所屬類為MTT.ThirdAppInfoNew,方法聲明為V,方法名為。

image

8.DexTypeClassDefItem(類定義)

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結構的偏移 */
};

struct DexClassData {
    DexClassDataHeader header; /* 指定字段與方法的個數 */
    DexField* staticFields;    /* 靜態字段,DexField結構 */
    DexField* instanceFields;  /* 實例字段,DexField結構 */
    DexMethod* directMethods;  /* 直接方法,DexMethod結構 */
    DexMethod* virtualMethods; /* 虛方法,DexMethod結構 */

struct DexClassDataHeader {
    u4 staticFieldsSize;  /* 靜態字段個數 */
    u4 instanceFieldsSize; /* 實例字段個數 */
    u4 directMethodsSize;  /* 直接方法個數 */
    u4 virtualMethodsSize; /* 虛方法個數 */
};

struct DexField {
    u4 fieldIdx;    /* 指向DexFieldId的索引 */
    u4 accessFlags; /* 訪問標志 */
};

struct DexMethod {
    u4 methodIdx;   /* 指向DexMethodId的索引 */
    u4 accessFlags; /* 訪問標志 */
    u4 codeOff;     /* 指向DexCode結構的偏移 */
};

struct DexCode {
    u2 registersSize;   /* 使用的寄存器個數 */
    u2 insSize;         /* 參數個數 */
    u2 outsSize;        /* 調用其他方法時使用的寄存器個數 */
    u2 triesSize;       /* Try/Catch個數 */
    u4 debugInfoOff;    /* 指向調試信息的偏移 */
    u4 insnsSize;       /*指令集個數,以2字節為單位 */
    u2 insns[1];        /* 指令集 */


DexClassDef
classIdx:索引值,表明類的類型。下圖中值為0x6,指向類型MTT.ThirdAppInfoNew。 accessFlags:類的訪問標志,它是以ACC_開頭的一個枚舉值。具體定義可以參考這裡。下圖中值為0x11,表示同時具有ACC_PUBLIC和ACC_FINAL。 superclassIdx:父類類型索引值。下圖中值為0x1B13,指向java.lang.Object類。 interfacesOff:如果類中含有接口聲明或實現,interfaceOff會指向一個DexTypeList結構,否則這裡的值為0。圖中值為0x4B9894(4954260),指向的DexTypeList結構為java.lang.Cloneable。 sourceFileIdx:字符串索引值,表示類所在的源文件名稱。圖中值為NO_INDEX(0xffffffff),表示該值丟失。 annotationsOff:指向注解目錄結構,根據類型不同會有注解類、注解方法、注解字段與注解參數,如果類中沒有注解,這裡的值則為0。圖中值為0,表示類中沒有注解。 classDataOff:指向DexClassData結構,它是類的數據部分。圖中為0x65B97A。 staticValuesOff:指向DexEncodedArray結構,記錄了類中的靜態數據。圖中為0,表示類中沒有靜態數據。
image

DexClassData

header:一個DexClassDataHeader結構,指定字段與方法的個數。如下圖中的staticFieldsSize為0,表示沒有靜態字段;instanceFieldsSize為0xC,表示有12個實例字段;directMethodsSize為0x1,表示有一個直接方法;virtualMethodsSize為0x0,表示沒有虛方法。

image

staticFields*:指向一個DexField結構,表示靜態字段的類型與訪問標志。由於本例沒有靜態字段,因此該結構無效。

directMethods*:指向一個DexField結構,表示實例字段的類型與訪問標志。如下圖本例中有一個實例字段。
image

directMethods*:指向一個DexMethod結構,表示直接方法的原型、名稱、訪問標志、代碼數據塊。。如下圖本例中有12個實例字段。 image

virtualMethods*:指向一個DexMethod結構,表示虛方法的原型、名稱、訪問標志、代碼數據塊。由於本例沒有靜態字段,因此該結構無效。

DexField

fieldIdx:指向DexFieldId的索引,表示字段的所屬類、字段類型和字段名。 accessFlags:訪問標志。

DexMethod

methodIdx:指向DexMethodId的索引,表示方法的所在類、方法的聲明和方法名。 accessFlags:訪問標志。 codeOff:指向DexCode結構的偏移,圖中為0x138A34。

DexCode

registersSize:該方法使用的寄存器個數。下圖中為3。 insSize:該方法的參數個數,對應smali語法中的”.register”指令。下圖中為1。 outsSize:該方法調用其他方法時,對應smali語法中的”.paramter”指令。例如現在有一個方法,使用了5個寄存器,其中有2個為參數,而該方法調用了另一個方法,後者使用了20個寄存器,那麼Dalvik虛擬機在分配時,會在分配自身方法寄存器空間時加上那20個寄存器空間。下圖中為1。 triesSize:方法中Try/Catch的個數。 debugInfoOff:如果dex文件保留了調試信息,debugInfoOff字段會指向它。 insnsSize:指令個數,以2字節為單位。 insns[1]:真正的指令部分。

image

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved