<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ### **一張圖搞懂dex** [大圖這里](https://box.kancloud.cn/a60feaef41587bfe91f3bb1a10be6bd0_4287x1818.jpg) ![](https://box.kancloud.cn/a60feaef41587bfe91f3bb1a10be6bd0_4287x1818.jpg) :-: 圖1 dex 當然也可以通過下面的圖12 DexFile的文件格式,了解更清楚。 ### **DEX文件詳解** * 什么是dex文件? * 如何生成一個dex文件 * dex文件的作用 * dex文件格式詳解 #### **什么是dex文件?** dex文件是Android系統中的一種文件,是一種特殊的數據格式,和APK、jar 等格式文件類似。 能夠被DVM識別,加載并執行的文件格式。 簡單說就是優化后的android版.exe。每個apk安裝包里都有。包含應用程序的全部操作指令以及運行時數據。 相對于PC上的java虛擬機能運行.class;android上的Davlik虛擬機能運行.dex。 當java程序編譯成class后,還需要使用dx工具將所有的class文件整合到一個dex文件,目的是其中各個類能夠共享數據,在一定程度上降低了冗余,同時也是文件結構更加經湊,實驗表明,dex文件是傳統jar文件大小的50%左右 ![](https://box.kancloud.cn/9825f74b154c260a54c987ef417a15dc_604x514.png) :-: 圖2 apk中的dex文件 **為何要研究dex格式**?因為dex里面包含了所有app代碼,利用反編譯工具可以獲取java源碼。理解并修改dex文件,就能更好的apk破解和防破解。 使用dex文件的最大目的是實現安全管理,但在追求安全的前提下,一定要注意對dex文件實現優化處理。 **注意**:并不是只有Java才可以生成dex文件,C和C++也可以生成dex文件 #### **如何生成一個dex文件?** 1. 通過IDE自動幫我們build生成 2. 手動通過dx命令去生成dex文件 在待測試的class文件目錄下(我將TestMain.class放到了F盤根目錄下),執行命令`dx --dex --output TestMain.dex TestMain.class`,就會生成TestMain.dex文件。 3. 手動運行dex文件在手機 在待測試的dex文件目錄下(我將TestMain.class放到了F盤根目錄下),通過`adb push TestMain.dex /storage/emulated/0`命令,然后通過`adb shell`命令進入手機,后執行`dalvikvm -cp /sdcard/TestMain.dex TestMain`,就會打印出 ~~~ Hello World! ~~~ 如下圖3所示(使用AS的終端,沒有用Windows的cmd命令) ![](https://box.kancloud.cn/e702670e8c32e4c2137e06404bb94e29_686x247.jpg) :-: 圖3 手動運行dex文件 **注意**: - 環境變量的配置,dex在SDK目錄下的build-tools目錄下有很多版本,這里可以選擇最新版本目錄下dx.bat配置到環境變量path路徑下;同樣,adb命令也同樣配置。 - 運行完dex文件,可以通過exit退出手機,電腦本地盤符 #### **查看dex文件** [大圖這里](https://box.kancloud.cn/10e05e43d5249e53292c874a189ca716_909x547.jpg) ![](https://box.kancloud.cn/10e05e43d5249e53292c874a189ca716_909x547.jpg) :-: 圖4 dex文件概貌 **通過010Editor工具(圖片來自網絡)** [大圖這里](https://box.kancloud.cn/8208f0e93ba1fc2e20ae480e31865dca_636x585.png) ![](https://box.kancloud.cn/8208f0e93ba1fc2e20ae480e31865dca_636x585.png) :-: 圖5 注:圖片來自網絡 下圖6是TestMain.dex通過010Editor工具得到的 [大圖這里](https://box.kancloud.cn/30cf8d988e17bb86e2dd619b89022153_599x909.jpg) ![](https://box.kancloud.cn/30cf8d988e17bb86e2dd619b89022153_599x909.jpg) :-: 圖6 010Editor 檢測TestMain.dex結果 圖7是通過010Editor工具檢測TestMain.dex得到的 Template Result結果 [大圖這里](https://box.kancloud.cn/5b1f794e214d709e553756fd6d2b4bc2_1659x862.jpg) ![](https://box.kancloud.cn/5b1f794e214d709e553756fd6d2b4bc2_1659x862.jpg) :-: 圖7 010Editor檢測TemplateResult結果 **通過dexdump 命令查看(注意)** 利用build-tools 下的dexdump 命令查看,`dexdump -d -l plain TestMain.dex`,得到下面的結果 ~~~ F:\>dexdump -d -l plain TestMain.dex Processing 'TestMain.dex'... Opened 'TestMain.dex', DEX version '035' Class #0 - Class descriptor : 'LTestMain;' Access flags : 0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LTestMain;) name : 'mX' type : 'I' access : 0x0001 (PUBLIC) Direct methods - #0 : (in LTestMain;) name : '<init>' type : '()V' access : 0x10001 (PUBLIC CONSTRUCTOR) code - registers : 2 ins : 1 outs : 1 insns size : 7 16-bit code units 00015c: |[00015c] TestMain.<init>:()V 00016c: 7010 0400 0100 |0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0004 000172: 1200 |0003: const/4 v0, #int 0 // #0 000174: 5910 0000 |0004: iput v0, v1, LTestMain;.mX:I // field@0000 000178: 0e00 |0006: return-void catches : (none) positions : 0x0000 line=11 0x0003 line=3 0x0006 line=12 locals : 0x0000 - 0x0007 reg=1 this LTestMain; #1 : (in LTestMain;) name : 'main' type : '([Ljava/lang/String;)V' access : 0x0009 (PUBLIC STATIC) code - registers : 4 ins : 1 outs : 2 insns size : 16 16-bit code units 00017c: |[00017c] TestMain.main:([Ljava/lang/String;)V 00018c: 2200 0100 |0000: new-instance v0, LTestMain; // type@0001 000190: 7010 0000 0000 |0002: invoke-direct {v0}, LTestMain;.<init>:()V // method@0000 000196: 6e10 0200 0000 |0005: invoke-virtual {v0}, LTestMain;.test:()V // method@0002 00019c: 6201 0100 |0008: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0001a0: 1a02 0100 |000a: const-string v2, "Hello World!" // string@0001 0001a4: 6e20 0300 2100 |000c: invoke-virtual {v1, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0003 0001aa: 0e00 |000f: return-void catches : (none) positions : 0x0000 line=6 0x0005 line=7 0x0008 line=8 0x000f line=9 locals : 0x0005 - 0x0010 reg=0 testMainObject LTestMain; 0x0000 - 0x0010 reg=3 args [Ljava/lang/String; Virtual methods - #0 : (in LTestMain;) name : 'test' type : '()V' access : 0x0001 (PUBLIC) code - registers : 1 ins : 1 outs : 0 insns size : 1 16-bit code units 0001ac: |[0001ac] TestMain.test:()V 0001bc: 0e00 |0000: return-void catches : (none) positions : 0x0000 line=15 locals : 0x0000 - 0x0001 reg=0 this LTestMain; source_file_idx : 8 (TestMain.java) ~~~ - **registers**:Dalvik 最初目標是運行在以ARM 做CPU 的機器上的,**ARM 芯片的一個主要特點是寄存器多**。**寄存器多的話有好處,就是可以把操作數放在寄存器里,而不是像傳統VM 一樣放在棧中**。自然,**操作寄存器是比操作內存(棧嘛,其實就是一塊內存區域)快**。**registers 變量表示該方法運行過程中會使用多少個寄存器**。 - **ins**:輸入參數對應的個數 - **outs**:此函數內部調用其他函數,需要的參數個數。 - **insns size**:以4 字節為單位,代表該函數字節碼的長度(類似Class 文件的code[]數組) 更多內容參考:官網介紹----->[Dalvik 可執行文件格式](https://source.android.com/devices/tech/dalvik/dex-format.html) #### **dex文件的作用** 記錄**整個工程中所有類的信息**,記住的整個工程所有類的信息 #### **dex文件格式詳解** - 一種8位字節的二進制流文件 - 各個數據按順序緊密的排列,無間隙 - 整個應用中所有的Java源文件都放在一個dex中 [大圖這里](https://box.kancloud.cn/53f6fab5d77f3bb12a273bd0b01913fd_537x622.jpg) ![](https://box.kancloud.cn/53f6fab5d77f3bb12a273bd0b01913fd_537x622.jpg) :-: 圖8 dex文件結構 上圖中的**文件頭**部分,記錄了dex文件的信息,所有字段大致的一個分部;**索引區**部分,主要包含字符串、類型、方法原型、域、方法的索引;索引區最終又被存儲在**數據區**,其中鏈接數據區,主要存儲動態鏈接庫,so庫的信息。 源碼:**/dalvik/libdex/DexFile.h:DexFile** ~~~ struct DexFile { /* directly-mapped "opt" header */ const DexOptHeader* pOptHeader; /* pointers to directly-mapped structs and arrays in base DEX */ const DexHeader* pHeader; const DexStringId* pStringIds; const DexTypeId* pTypeIds; const DexFieldId* pFieldIds; const DexMethodId* pMethodIds; const DexProtoId* pProtoIds; const DexClassDef* pClassDefs; const DexLink* pLinkData; }; ~~~ 具體可查看[Android源碼官網](https://source.android.com/devices/tech/dalvik/dex-format)的關于dex文件結構的詳解,如下圖9([大圖這里](https://box.kancloud.cn/227684fb298d4c683bb0a6a5222a7bf3_856x765.jpg)) ![](https://box.kancloud.cn/227684fb298d4c683bb0a6a5222a7bf3_856x765.jpg) :-: 圖9 dex文件結構詳解 **總結**: |數據名稱| 解釋| | --- | --- | |header |dex文件頭部,記錄整個dex文件的相關屬性| |string_ids| 字符串數據索引,記錄了每個字符串在數據區的偏移量| |type_ids |類似數據索引,記錄了每個類型的字符串索引| |proto_ids |原型數據索引,記錄了方法聲明的字符串,返回類型字符串,參數列表| |field_ids |字段數據索引,記錄了所屬類,類型以及方法名| |method_ids| 類方法索引,記錄方法所屬類名,方法聲明以及方法名等信息| |class_defs |類定義數據索引,記錄指定類各類信息,包括接口,超類,類數據偏移量| |data |數據區,保存了各個類的真是數據| |link_data |連接數據區| **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 字節| **/dalvik/libdex/DexFile.h**中定義如下 ~~~ typedef uint8_t u1; typedef uint16_t u2; typedef uint32_t u4; typedef uint64_t u8; typedef int8_t s1; typedef int16_t s2; typedef int32_t s4; typedef int64_t s8; ~~~ **LEB128** LEB128(“Little-Endian Base 128”)表示任意有符號或無符號整數的可變長度編碼。該格式借鑒了 [DWARF3](http://dwarfstd.org/Dwarf3Std.php) 規范。在 .dex 文件中,LEB128 僅用于對 32 位數字進行編碼。 每個 LEB128 編碼值均由 1-5 個字節組成,共同表示一個 32 位的值。每個字節均已設置其最高有效位(序列中的最后一個字節除外,其最高有效位已清除)。每個字節的剩余 7 位均為有效負荷,即第一個字節中有 7 個最低有效位,第二個字節中也是 7 個,依此類推。對于有符號 LEB128 (sleb128),序列中最后一個字節的最高有效負荷位會進行符號擴展,以生成最終值。在無符號情況 (uleb128) 下,任何未明確表示的位都會被解譯為 0。 [大圖這里](https://box.kancloud.cn/ff0794c58d01df4845dc3939385f0a3f_855x87.jpg) ![](https://box.kancloud.cn/ff0794c58d01df4845dc3939385f0a3f_855x87.jpg) :-: 圖10 雙字節 LEB128 值的按位圖 變量 uleb128p1 用于表示一個有符號值,其表示法是編碼為 uleb128 的值加 1。這使得編碼 -1(或被視為無符號值 0xffffffff)成為一個單字節(但沒有任何其他負數),并且該編碼在下面這些明確說明的情況下非常實用:所表示的數值必須為非負數或 -1(或 0xffffffff);不允許任何其他負值(或不太可能需要使用較大的無符號值)。 以下是這類格式的一些示例: |編碼序列 | As sleb128 | As uleb128 | As uleb128p1 | | --- | --- | --- | --- | |00 |0 | 0 | -1 | |01 |1 |1 |0 | |7f |-1 | 127 | 126 | |80 | 7f |-128 |16256 |16255 | **dex文件頭** Dex文件頭主要包括校驗和以及其他結構的偏移地址和長度信息。 源碼位于 **/dalvik/libdex/DexFile.h:DexHeader** ~~~ 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; }; ~~~ 具體詳解如下圖5所示 [大圖這里](https://box.kancloud.cn/c676a551dbf5a9a2ff1c999c652c284d_618x480.png) ![](https://box.kancloud.cn/c676a551dbf5a9a2ff1c999c652c284d_618x480.png) :-: 圖11 dex文件頭信息 **各個字段詳解摘要** **mapOff 字段** 指定 DexMapList 結構距離 Dex 頭的偏移 DexMapList 結構體: ~~~ struct DexMapList { u4 size; // DexMapItem 的個數 DexMapItem list[1]; // DexMapItem 結構 }; ~~~ * size:表示接下來有多少個 DexMapItem * list:是一個 DexMapItem 結構體數組 DexMapItem 結構體: ~~~ struct DexMapItem { u2 type; // kDexType 開頭的類型 u2 unused; // 未使用,用于對齊 u4 size; // 指定類型的個數 u4 offset; // 指定類型數據的文件偏移 }; ~~~ type:一個枚舉常量 ~~~ enum { kDexTypeHeaderItem = 0x0000, // 對應 DexHeader kDexTypeStringIdItem = 0x0001, // 對應 stringIdsSize 與 stringIdsOff 字段 kDexTypeTypeIdItem = 0x0002, // 對應 typeIdsSize 與 typeIdsOff 字段 kDexTypeProtoIdItem = 0x0003, // 對應 protoIdsSize 與 protoIdsOff 字段 kDexTypeFieldIdItem = 0x0004, // 對應 fieldIdsSize 與 fieldIdsOff 字段 kDexTypeMethodIdItem = 0x0005, // 對應 methodIdsSize 與 methodIdsOff 字段 kDexTypeClassDefItem = 0x0006, // 對應 classDefsSize 與 classDefsOff 字段 kDexTypeMapList = 0x1000, kDexTypeTypeList = 0x1001, kDexTypeAnnotationSetRefList = 0x1002, kDexTypeAnnotationSetItem = 0x1003, kDexTypeClassDataItem = 0x2000, kDexTypeCodeItem = 0x2001, kDexTypeStringDataItem = 0x2002, kDexTypeDebugInfoItem = 0x2003, kDexTypeAnnotationItem = 0x2004, kDexTypeEncodeArrayItem = 0x2005, kDexTypeAnnotationsDirectoryItem = 0x2006 }; ~~~ * size:指定類型的個數 * offset:指定類型數據的偏移 **DexStringId 結構體(stringIdsSize 與 stringIdsOff 字段)** ~~~ typedef struct _DexStringId { u4 stringDataOff; // 指向 MUTF-8 字符串的偏移 }DexStringId, *PDexStringId; ~~~ MUTF-8 編碼: 1) 使用 1~3 字節編碼長度 2) 大于 16 位的 Unicode 編碼 U+10000~U+10FFFF 使用 3 字節來編碼 3) U+0000 采用 2 字節編碼 4) 采用空字符 null 作為結尾 5) 第一個字節存放字節個數(不包含自已) **DexTypeId 結構體(typeIdsSize 與 typeIdsOff 字段)** 是一個類型結構體 ~~~ typedef struct _DexTypeId { u4 descriptorIdx; // 指向 DexStringId 列表的索引 }DexTypeId, *PDexTypeId; ~~~ * **descriptorIdx**:指向 DexStringId 列表的索引,它對應的字符串代表了具體類的類型 **DexProtoId 結構體(protoIdsSize 與 protoIdsOff 字段)** 是一個方法聲明結構體,方法聲明 = 返回類型 + 參數列表 ~~~ typedef struct _DexProtoId { u4 shortyIdx; // 方法聲明字符串,指向 DexStringId 列表的索引 u4 returnTypeIdx; // 方法返回類型字符串,指向 DexStringId 列表的索引 u4 parametersOff; // 方法的參數列表,指向 DexTypeList 結構體的偏移 }DexProtoId, *PDexProtoId; ~~~ * shortyIdx:方法聲明字符串,方法聲明 = 返回類型 + 參數列表 * returnTypeIdx:方法返回類型字符串 * parametersOff:指向一個 DexTypeList 結構體,存放了方法的參數列表 DexTypeList 結構體: ~~~ typedef struct _DexTypeList { u4 size; // 接下來 DexTypeItem 的個數 DexTypeItem* list; // DexTypeItem 結構 }DexTypeList, *PDexTypeList; ~~~ * size:接下來 DexTypeItem 的個數 * list:是一個 DexTypeItem 結構體數組 DexTypeItem 結構體: ~~~ typedef struct _DexTypeItem { u2 typeIdx; // 指向 DexTypeId 列表的索引 }DexTypeItem, *PDexTypeItem; ~~~ typeIdx:DexTypeId 列表的索引 **DexFieldId 結構體(fieldIdsSize 與 fieldIdsOff 字段)** 指明了字段所有的類、字段的類型以及字段名 ~~~ typedef struct _DexFieldId { u2 classIdx; // 類的類型,指向 DexTypeId 列表的索引 u2 typeIdx; // 字段的類型,指向 DexTypeId 列表的索引 u4 nameIdx; // 字段名,指向 DexStringId 列表的索引 }DexFieldId, *PDexFieldId; ~~~ * classIdx:類的類型 * typeIdx:字段的類型 * nameIdx:字段名 **DexMethodId 結構體(methodIdsSize 與 methodIdsOff 字段)** 方法結構體 ~~~ typedef struct _DexMethodId { u2 classIdx; // 類的類型,指向 DexTypeId 列表的索引 u2 protoIdx; // 聲明的類型,指向 DexProtoId 列表的索引 u4 nameIdx; // 方法名,指向 DexStringId 列表的索引 }DexMethodId, *PDexMethodId; ~~~ * classIdx:類的類型 * protoIdx:聲明的類型 * nameIdx:方法名 **DexClassDef 結構體(classDefsSize 和 classDefsOff 字段)** 類結構體 ~~~ typedef struct _DexClassDef { u4 classIdx; // 類的類型,指向 DexTypeId 列表的索引 u4 accessFlags; // 訪問標志 u4 superclassIdx; // 父類類型,指向 DexTypeId 列表的索引 u4 interfacesOff; // 接口,指向 DexTypeList 的偏移,否則為0 u4 sourceFileIdx; // 源文件名,指向 DexStringId 列表的索引 u4 annotationsOff; // 注解,指向 DexAnnotationsDirectoryItem 結構,或者為 0 u4 classDataOff; // 指向 DexClassData 結構的偏移,類的數據部分 u4 staticValuesOff; // 指向 DexEncodedArray 結構的偏移,記錄了類中的靜態數據,主要是靜態方法 }DexClassDef, *PDexClassDef; ~~~ * classIdx:類的類型,指向 DexTypeId 列表的索引 * accessFlags:訪問標志,它是以ACC_開頭的枚舉值 * superclassIdx:父類類型,指向 DexTypeId 列表的索引 * interfacesOff:接口,指向 DexTypeList 的偏移,如果沒有,則為 0 * sourceFileIdx:源文件名,指向 DexStringId 列表的索引 * annotationsOff:注解,指向 DexAnnotationsDirectoryItem 結構,或者為 0 * classDataOff:指向 DexClassData 結構的偏移,類的數據部分 * staticValuesOff:指向 DexEncodeArray 結構的偏移,記錄了類中的靜態數據,沒有則為 0 DexClassData 結構體: ~~~ typedef struct _DexClassData { DexClassDataHeader header; // 指定字段與方法的個數 DexField* staticFields; // 靜態字段,DexField 結構 DexField* instanceFields; // 實例字段,DexField 結構 DexMethod* directMethods; // 直接方法,DexMethod 結構 DexMethod* virtualMethods; // 虛方法,DexMethod 結構 }DexClassData, *PDexClassData; ~~~ * header:DexClassDataHeader 結構體,指定字段與方法的個數 * staticFields:靜態字段,DexField 結構體數組 * instanceFields:實例字段,DexField 結構體數組 * directMethods:直接方法,DexMthod 結構體數組 * virtualMethods:虛方法,DexMethod 結構體數組 DexClassDataHeader 結構體: ~~~ typedef struct _DexClassDataHeader { uleb128 staticFieldsSize; // 靜態字段個數 uleb128 instanceFieldsSize; // 實例字段個數 uleb128 directMethodsSize; // 直接方法個數 uleb128 virtualMethodsSize; // 虛方法個數 }DexClassDataHeader, *PDexClassDataHeader; ~~~ * staticFieldsSize:靜態字段個數 * instanceFieldsSize:實例字段個數 * directMethodsSize:直接方法個數 * virtualMethodsSize:虛方法個數 DexField 結構體: ~~~ typedef struct _DexField { uleb128 fieldIdx; // 指向 DexFieldId 的索引 uleb128 accessFlags; // 訪問標志 }DexField, *PDexField; ~~~ * fieldIdx:字段描述,指向 DexFieldId 的索引 * accessFlags:訪問標志 DexMethod 結構體: ~~~ typedef struct _DexMethod { uleb128 methodIdx; // 指向 DexMethodId 的索引 uleb128 accessFlags; // 訪問標志 uleb128 codeOff; // 指向 DexCode 結構的偏移 }DexMethod, *PDexMethod; ~~~ * methodIdx:方法描述,指向 DexMethodId 的索引 * accessFlags:訪問標志 * codeOff:指向 DexCode 結構的偏移 DexCode 結構體: ~~~ typedef struct _DexCode { u2 registersSize; // 使用的寄存器個數 u2 insSize; // 參數個數 u2 outsSize; // 調用其他方法時使用的寄存器個數 u2 triesSize; // Try/Catch 個數 u4 debbugInfoOff; // 指向調試信息的偏移 u4 insnsSize; // 指令集個數,以 2 字節為單位 u2* insns; // 指令集 }DexCode, *PDexCode; ~~~ 還有一些不太常見的結構體,要用的時候再去看看就行了。Dex 文件的整體結構就這樣,就是一個多層索引的結構。 **string_ids(字符串索引)** 這一區域存儲的是Dex文件字符串資源的索引信息,該索引信息是目標字符串在Dex文件數據區所在的真實物理偏移量。 源碼位于 **/dalvik/libdex/DexFile.h:DexStringId** ~~~ struct DexStringId { u4 stringDataOff; /* file offset to string_data_item */ }; ~~~ **stringDataOff**記錄了目標字符串在Dex文件中的實際偏移量,虛擬機想讀取該字符串時,只需將Dex文件在內存中的起始地址加上stringDataOff所指的偏移量,就是該字符串在內存中的實際物理地址。 在Dex文件中,每個每個字符串對應一個**DexStringId**,大小4B。另外虛擬機通過DexHeader中的**String_ids_size**獲得當前Dex文件中的字符串的總數,通過乘法就可對該索引資源進行訪問。 **DexLink** ~~~ struct DexLink { u1 bleargh; }; ~~~ #### **DexFile 在內存中的映射** 在Android系統中, java 源文件會被編譯為“ .jar ” 格式的dex類型文件, 在代碼中稱為dexfile 。在加載Class 之前, 必先讀取相應的jar文件。通常我們使用read()函數來讀取文件中的內容。但在Dalvik中使用mmap() 函數。和read()不同, mmap()函數會將dex文件映射到內存中,這樣通過普通的內存讀取操作即可訪問dexfile中的內容。 Dexfile的文件格式如圖12 所示, 主要有三部分組成:頭部,索引,數據。通過頭部可知索引的位置和數同,可知數據區的起始位置。其中classDefsOff 指定了ClassDef 在文件的起始位置, dataOff 指定了數據在文件的起始位置, ClassDef 即可理解為Class 的索引。通過讀取ClassDef 可獲知Class 的基本信息,其中classDataOff 指定了Class 數據在數據區的位置。 [大圖這里](https://box.kancloud.cn/31332ada4045c4713b69ae041c03cca9_901x751.jpg) ![](https://box.kancloud.cn/31332ada4045c4713b69ae041c03cca9_901x751.jpg) :-: 圖12 DexFile的文件格式 在將dexfile文件映射到內存后,會調用dexFileParse()函數對其分析,分析的結果存放于名為DexFile的數據結構中。DexFile 中的baseAddr指向映射區的起始位置, pClassDefs 指向ClassDefs(即class索引)的起始位置。由于在查找class 時,都是使用class的名字進行查找的,所以為了加快查找速度, 創建了一個hash表。在hash表中對class 名字進行hash,并生成index。這些操作都是在對文件解析時所完成的,這樣雖然在加載過程中比較耗時,但是在運行過程中可節省大量查找時間。 解析完后, 接下來開始加載class文件。在此需要將加載類用ClassObject來保存,所以在此需要先分析和ClassObject 相關的幾個數據結構。 首先在文件Object.h 中可以看到如下對結構體Object 的定義。(android2.3.7源碼) ~~~ typedef struct Object { /* ptr to class object */ ClassObject* clazz; /* * A word containing either a "thin" lock or a "fat" monitor. See * the comments in Sync.c for a description of its layout. */ u4 lock; } Object; ~~~ 通過結構體Object定義了基本類的實現,這里有如下兩個變量。 * lock : 對應Obejct 對象中的鎖實現,即notify wait 的處理。 * clazz : 是結構體指針,姑且不看結構體內容,這里用了指針的定義。 下面會有更多的結構體定義: ~~~ struct DataObject { Object obj; /* MUST be first item */ /* variable #of u4 slots; u8 uses 2 slots */ u4 instanceData[1]; }; struct StringObject { Object obj; /* MUST be first item */ /* variable #of u4 slots; u8 uses 2 slots */ u4 instanceData[1]; }; ~~~ 我們看到最熟悉的一個詞StringObject ,把這個結構體展開后是下面的樣子。 ~~~ struct StringObject { /* ptr t o class object */ ClassObject* clazz ; /* variable #of u4 slots; u8 uses 2 slots */ u4 lock; u4 instanceData[1]; }; ~~~ 由此不難發現, 任何對象的內存結構體中第一行都是Object結構體,而這個結構體第一個總是一個ClassObejct,第二個總是lock 。按照C++中的技巧,這些結構體可以當成Object結構體使用,因此所有的類在內存中都具有“對象”的功能,即可以找到一個類(ClassObject),可以有一個鎖(lock) 。 StringObject是對String類進行管理的數據對象,ArrayObejct是數據相關的管理。 #### **ClassObject-Class 在加載后的表現形式** 在解析完文件后, 接下來需要加載Class 的具體內容。在Dalvik中, 由數據結構ClassObject負責存放加載的信息。如圖13所示,加載過程會在內存中alloc幾個區域,分別存放directMethods 、virtualMethods 、sfields 、ifields 。這些信息是從dex 文件的數據區中讀取的,首先會讀取Class 的詳細信息,從中獲得directMethod 、virtua!Method 、sfield 、ifield 等的信息,然后再讀取。在此需要注意, 在C lassObj ect 結構中有個名為super 的成員,通過super成員可以指向它的超類。 [大圖這里](https://box.kancloud.cn/c2f4f23796b3858cf8f7813ad30dedf5_965x623.jpg) ![](https://box.kancloud.cn/c2f4f23796b3858cf8f7813ad30dedf5_965x623.jpg) 圖13 加載過程 #### **Android dex 文件優化** 對Android dex 文件進行優化來說, 需要注意的一點是dex文件的結構是緊湊的,但是我們**還是要想方設法地進行提高程序的運行速度,我們就仍然需要對dex文件進行進一步優化**。 調整所有字段的字節序( LITTLE_ENDIAN),和對齊結構中的每一個域來驗證dex文件中的所有類,并對一些特定的類進行優化或對方法里的操作碼進行優化。優化后的文件大小會有所增加, 大約是原Android dex文件的1~4 倍。 **優化時機** 優化發生的時機有兩個: * 對于預置應用來說,可以在系統編譯后,生成優化文件,以ODEX 結尾。這樣在發布時除APK文件(不包含dex)以外,還有一個相應的Android dex 文件。 * 對于非預置應用, 包含在APK文件里的dex 文件會在運行時被優化,優化后的文件將被保存在緩存中。 如下圖14所示代碼調用流程 ![](https://box.kancloud.cn/8ffe4ecac7de2e17040b86298ede8e03_410x500.png) 圖14 代碼調用流程 **每一個Android應用都運行在一個Dalvik虛擬機實例里,而每一個虛擬機實例都是一個獨立的進程空間**。虛擬機的線程機制,內存分配和管理, Mutex等都是依賴底層操作系統而實現的。 **所有Android應用的線程都對應一個Linux線程(可參考----[理解Android線程創建流程](http://www.hmoore.net/alex_wsc/androidsystem/403698))**,虛擬機因而可以更多地依賴操作系統的線程調度和管理機制。**不同的應用在不同的進程空間里運行,加之對不同來源的應用都使用不同的Linux用戶來運行,可以最大限度地保護應用的安全和獨立運行。** **Zygote是一個虛擬機進程,同時也是一個虛擬機實例的孵化器,每當系統要求執行一個Android應用程序,Zygote就會孵化出一個子進程來執行該應用程序**。這樣做的好處顯而易見:**Zygote進程是在系統啟動時產生的,它會完成虛擬機的初始化,庫的載,預置類庫的加載和初始化等操作,而在系統需要一個新的虛擬機實例時,Zygote通過復制自身,最快速地提供一個虛擬機實例。另外,對于一些只讀的系統庫,所有虛擬機實例都和Zygote 共享一塊內存區域,大大節省了內存開銷**。 Android 應用所使用的編程語言是Java語言,和Java SE 一樣,編譯時使用Oracle JDK 將Java源程序編程成標準的Java 字節碼文件(. class 文件)。而后通過工具軟件DX 把所有的字節碼文件轉成Android dex 文件(classes . dex) 。最后使用Android 打包工具(aapt)將dex 文件、資源文件以及AndroidManifest.xml 文件(二進制格式)組合成一個應用程序包(APK) 。應用程序包可以被發布到手機上運行。 ![](https://box.kancloud.cn/7ee01b864f369728d47411970da7c950_717x379.png) 圖15 Android應用編譯及運行流程 #### **odex 介紹** odex 是Optimized dex 的簡寫,也就是優化后的dex 文件。**為什么要優化呢?主要還是為了提高Dalvik 虛擬機的運行速度**。但是odex 不是簡單的、通用的優化,而是在其優化過程中,依賴系統已經編譯好的其他模塊,簡單點說: * 從Class 文件到dex 文件是針對Android 平臺的一種優化,是一種通用的優化。優化過程中,唯一的輸入是Class 文件。 * odex 文件就是dex 文件具體在某個系統(不同手機,不同手機的OS,不同版本的OS 等)上的優化。**odex 文件的優化依賴系統上的幾個核心模塊**( 由BOOTCLASSPATH 環境變量給出, 一般是/system/framework/下的jar 包,尤其是core.jar)。odex 的優化就好像是把中那些本來需要在執行過程中做的類校驗、調用其他類函數時的解析等工作給提前處理了。 通過利用dexopt得到test.odex,接著利用dexdump得到其內容,最后可以利用Beyond Compare比較這兩個文件的差異。 如下圖所示 ![](http://cdn2.infoqstatic.com/statics_s1_20151215-0056_2/resource/articles/android-in-depth-dalvik/zh/resources/image011.png) 圖16 test.dex 和test.odex 差異 圖16中,綠色框中是test.dex的內容,紅色框中是test.odex的內容,這也是兩個文件的差異內容: * test.dex中,TestMain類僅僅是PUBLIC的,但test.odex則增加了**VERIFIED和OPTIMIZED**兩項。**VERIFIED**是表示該類被校驗過了,至于校驗什么東西,以后再說。 * 然后就是一些方法的不同了。優化后的odex文件,一些字節碼指令變成了xxx-quick。比如圖中最后一句代碼對于的字節碼中,未優化前**invoke-virtual**指令表示從**method table**指定項(圖中是0002)里找到目標函數,而優化后的odex使用了**invoke-virtual-quick**表示從**vtable**中找到目標函數(圖中是000b)。 vtable是虛表的意思,一般在OOP實現中用得很多。vtable一定比methodtable快么?那倒是有可能。我個人猜測: * method表應該是每個dex文件獨有的,即它是基于dex文件的。 * 根據odex文件的生成方法(后面會講),我覺得vtable恐怕是把dex文件及依賴的類(比如Java基礎類,如Object類等)放一起進行了處理,最終得到一張大的vtable。這個odex文件依賴的一些函數都放在vtable中。運行時直接調用指定位置的函數就好,不需要再解析了。以上僅是我的猜測。 > **注意**: > **odex文件由dexopt生成,這個工具在SDK里沒有,只能由源碼生成**。odex文件的生成有三種方式: > * **preopt**:即OEM廠商(比如手機廠商),在制作鏡像的時候,就把那些需要放到鏡像文件里的jar包,APK等預先生成對應的odex文件,然后再把classes.dex文件從jar包和APK中去掉以節省文件體積。 > * **installd**:當一個apk安裝的時候,PackageManagerService會調用installd的服務,將apk中的class.dex進行處理。當然,這種情況下,APK中的class.dex不會被剔除。 > * **dalvik VM**:preopt是廠商的行為,可做可不做。如果沒有做的話,dalvik VM在加載一個dex文件的時候,會先生成odex。所以,dalvik VM實際上用得是odex文件。以后我們研究dalvik VM的時候會看到這部分內容。 > > 實際上dex轉odex是利用了dalvik vm,里邊也會運行dalvik vm的相關方法。 **總結**: 1. 以標準角度來看,Class文件是由Java VM規范定義的,所以通用性更廣。dex或者是odex只不過是規范在Android平臺上的一種具體實現罷了,而且dex/odex在很多地方也需要遵守規范。因為dex文件的來源其實還是Class文件。 2. 對于初學者而言,我建議了解Class文件的結構為主。另外,關于dex/odex的文件結構,除非有明確需求(比如要自己修改字節碼等),否則以了解原理就可以。而且,將來我們看到dalvik vm的實際代碼后,你會發現dex的文件內容還是會轉換成代碼里的那些你很熟悉的類型,數據結構。比如dex存儲字符串是一種優化后的方法,但是到vm代碼中,還不是只能用字符串來表示嗎? 3. 另外,你還會發現,Class、dex還是odex文件都存儲了很多源碼中的信息,比如類名、函數名、參數信息、成員變量信息等,而且直接用得是字符串。這和Native的二進制比起來,就容易看懂多了。 #### **參考鏈接** [深入理解Android之Java虛擬機Dalvik](http://blog.csdn.net/innost/article/details/50377905) [Androidsource之Dalvik 字節碼](https://source.android.com/devices/tech/dalvik/dalvik-bytecode) [Androidsource之Dalvik 可執行文件格式(dex文件)](https://source.android.com/devices/tech/dalvik/dex-format) [Android安全–Dex文件格式詳解](http://www.blogfshare.com/dex-format.html) [詳細描述了dex/odex指令的格式----->Dalvik opcodes](http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html) [解釋器中對 標號 的使用](http://www.cnblogs.com/openix/archive/2012/12/14/2818495.html) [A deep dive into DEX file format](https://elinux.org/images/d/d9/A_deep_dive_into_dex_file_format--chiossi.pdf) [Dex文件格式詳解](http://www.jianshu.com/p/f7f0a712ddfe) [android中Dex文件結構詳解](https://bbs.pediy.com/thread-203417.htm) [Dex文件及Dalvik字節碼格式解析](http://blog.csdn.net/u012342262/article/details/52559190) [Dex 文件格式詳解](http://blog.csdn.net/anhuizhuanjiao/article/details/49814549) [Dex文件格式詳解](http://blog.csdn.net/woblog/article/details/52106546) [Android關于Dex拆分(MultiDex)技術詳解](http://blog.csdn.net/tan6458/article/details/54315037)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看