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

Dex文件格式詳解

編輯:關於Android編程

什麼是dex文件

他是Android系統的可執行文件,包含應用程序的全部操作指令以及運行時數據。

由於dalvik是一種針對嵌入式設備而特殊設計的java虛擬機,所以dex文件與標准的class文件在結構設計上有著本質的區別

當java程序編譯成class後,還需要使用dx工具將所有的class文件整合到一個dex文件,目的是其中各個類能夠共享數據,在一定程度上降低了冗余,同時也是文件結構更加經湊,實驗表明,dex文件是傳統jar文件大小的50%左右

\

可以看見:
dex將原來class每個文件都有的共有信息合成一體,這樣減少了class的冗余

數據結構

類型 含義 u1 unit8_t,1字節無符號數 u2 unit16_t,2字節無符號數 u4 unit32_t,4字節無符號數 u8 unit64_t,8字節無符號數 sleb128 有符號LEB128,可變長度1~5 uleb128 無符號LEB128, uleb128p1 無符號LEB128值加1,

其中u1~u8很好理解,不理解的可以參考這裡,表示1到8個字節的無符號數,後面三個是dex特有的數據類型。

dex文件結構

首先從宏觀上來說dex的文件結果很簡單,實際上是由多個不同結構的數據體以首尾相接的方式拼接而成。如下圖:

數據名稱 解釋 header dex文件頭部,記錄整個dex文件的相關屬性 string_ids 字符串數據索引,記錄了每個字符串在數據區的偏移量 type_ids 類似數據索引,記錄了每個類型的字符串索引 proto_ids 原型數據索引,記錄了方法聲明的字符串,返回類型字符串,參數列表 field_ids 字段數據索引,記錄了所屬類,類型以及方法名 method_ids 類方法索引,記錄方法所屬類名,方法聲明以及方法名等信息 class_defs 類定義數據索引,記錄指定類各類信息,包括接口,超類,類數據偏移量 data 數據區,保存了各個類的真是數據 link_data 連接數據區

/dalvik/libdex/DexFile.h

定義如下:

struct DexFile {
    const DexHeader*    pHeader;
    const DexStringId*  pStringIds;
    const DexTypeId*    pTypeIds;
    const DexFieldId*   pFieldIds;
    const DexMethodId*  pMethodIds;
    const DexProtoId*   pProtoIds;
    const DexClassDef*  pClassDefs;
    const DexLink*      pLinkData;
}

注意:其中一些定義的字段是在內存中並沒有存到真是的dex文件中

header

簡單記錄了dex文件的一些基本信息,以及大致的數據分布。長度固定為0x70,其中每一項信息所占用的內存空間也是固定的,好處是虛擬機在處理dex時不用考慮dex文件的多樣性

字段名稱 偏移值 長度 說明 magic 0x0 8 魔數字段,值為”dex\n035\0” checksum 0x8 4 校驗碼 signature 0xc 20 sha-1簽名 file_size 0x20 4 dex文件總長度 header_size 0x24 4 文件頭長度,009版本=0x5c,035版本=0x70 endian_tag 0x28 4 標示字節順序的常量 link_size 0x2c 4 鏈接段的大小,如果為0就是靜態鏈接 link_off 0x30 4 鏈接段的開始位置 map_off 0x34 4 map數據基址 string_ids_size 0x38 4 字符串列表中字符串個數 string_ids_off 0x3c 4 字符串列表基址 type_ids_size 0x40 4 類列表裡的類型個數 type_ids_off 0x44 4 類列表基址 proto_ids_size 0x48 4 原型列表裡面的原型個數 proto_ids_off 0x4c 4 原型列表基址 field_ids_size 0x50 4 字段個數 field_ids_off 0x54 4 字段列表基址 method_ids_size 0x58 4 方法個數 method_ids_off 0x5c 4 方法列表基址 class_defs_size 0x60 4 類定義標中類的個數 class_defs_off 0x64 4 類定義列表基址 data_size 0x68 4 數據段的大小,必須4k對齊 data_off 0x6c 4 數據段基址

/dalvik/libdex/DexFile.h

定義如下:

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;
};

我們可以用:hexdump -c classes.dex查看dex單字節顯示的結果,如下:

0000000   d   e   x  \n   0   3   5  \0 022 217   ?   w   z   ? 031 221
0000010   ?  \f   ?   ?   ?   ?   ?   ? 217 235 200   z   ? 030   I   ?
0000020   ? 003  \0  \0   p  \0  \0  \0   x   V   4 022  \0  \0  \0  \0
0000030  \0  \0  \0  \0   ? 002  \0  \0 024  \0  \0  \0   p  \0  \0  \0
0000040  \b  \0  \0  \0   ?  \0  \0  \0 005  \0  \0  \0   ?  \0  \0  \0
0000050 001  \0  \0  \0 034 001  \0  \0 005  \0  \0  \0   $ 001  \0  \0
0000060 001  \0  \0  \0   L 001  \0  \0   8 002  \0  \0   l 001  \0  \0
0000070   l 001  \0  \0   t 001  \0  \0 201 001  \0  \0 204 001  \0  \0
0000080 222 001  \0  \0 226 001  \0  \0   ? 001  \0  \0   ? 001  \0  \0
0000090   ? 001  \0  \0   ? 001  \0  \0 004 002  \0  \0  \a 002  \0  \0
00000a0  \v 002  \0  \0     002  \0  \0   ( 002  \0  \0   . 002  \0  \0
00000b0   4 002  \0  \0   9 002  \0  \0   B 002  \0  \0   L 002  \0  \0
00000c0 003  \0  \0  \0 005  \0  \0  \0 006  \0  \0  \0  \a  \0  \0  \0
00000d0  \b  \0  \0  \0  \t  \0  \0  \0  \n  \0  \0  \0  \f  \0  \0  \0
00000e0 002  \0  \0  \0 003  \0  \0  \0  \0  \0  \0  \0 004  \0  \0  \0
00000f0 004  \0  \0  \0   x 002  \0  \0  \n  \0  \0  \0 006  \0  \0  \0
0000100  \0  \0  \0  \0  \v  \0  \0  \0 006  \0  \0  \0   x 002  \0  \0
0000110  \v  \0  \0  \0 006  \0  \0  \0   p 002  \0  \0 005  \0 001  \0
0000120 020  \0  \0  \0  \0  \0 004  \0 017  \0  \0  \0 001  \0 003  \0
0000130 021  \0  \0  \0 004  \0 002  \0  \0  \0  \0  \0 004  \0 001  \0
0000140  \r  \0  \0  \0 004  \0  \0  \0 022  \0  \0  \0  \0  \0  \0  \0
0000150 001  \0  \0  \0 002  \0  \0  \0  \0  \0  \0  \0   ?   ?   ?   ?
0000160  \0  \0  \0  \0   ? 002  \0  \0  \0  \0  \0  \0 006   <   i   n
0000170   i   t   >  \0  \v   H   e   l   l   o       W   o   r   l   d
0000180  \0 001   L  \0  \f   L   H   e   l   l   o   W   o   r   l   d
0000190   ;  \0 002   L   L  \0 025   L   j   a   v   a   /   i   o   /
00001a0   P   r   i   n   t   S   t   r   e   a   m   ;  \0 022   L   j
00001b0   a   v   a   /   l   a   n   g   /   O   b   j   e   c   t   ;
00001c0  \0 022   L   j   a   v   a   /   l   a   n   g   /   S   t   r
00001d0   i   n   g   ;  \0 031   L   j   a   v   a   /   l   a   n   g
00001e0   /   S   t   r   i   n   g   B   u   i   l   d   e   r   ;  \0
00001f0 022   L   j   a   v   a   /   l   a   n   g   /   S   y   s   t
0000200   e   m   ;  \0 001   V  \0 002   V   L  \0 023   [   L   j   a
0000210   v   a   /   l   a   n   g   /   S   t   r   i   n   g   ;  \0
0000220 006   a   p   p   e   n   d  \0 004   a   r   g   s  \0 004   m
0000230   a   i   n  \0 003   o   u   t  \0  \a   p   r   i   n   t   l
0000240   n  \0  \b   t   o   S   t   r   i   n   g  \0 016   ?   ? 231
0000250   ? 230   ?   ?   ? 200   ?   ?   ?   ? 211 213   ? 206 231   ?
0000260 232 204   s   m   a   l   i   ?   ? 236   ?   ? 213  \0  \0  \0
0000270 001  \0  \0  \0  \a  \0  \0  \0 001  \0  \0  \0 003  \0  \0  \0
0000280  \0  \0  \0  \0  \0  \0  \0  \0  \0 001 017  \a  \0  \0  \0  \0
0000290  \v  \0 001  \0 002  \0  \0  \0 210 002  \0  \0   (  \0  \0  \0
00002a0   b  \0  \0  \0  \0  \0  \0  \0  \0  \0 022   2 023 003   ?   ?
00002b0 030 004  \0  \0 001  \0  \0  \0  \0  \0 034 005 003  \0 001   &
00002c0   "  \a 004  \0   p 020 002  \0  \a  \0 032  \b 023  \0   n    
00002d0 003  \0 207  \0  \f  \a   n 020 004  \0  \a  \0  \f  \t   n    
00002e0 001  \0 220  \0 032 001 001  \0   n     001  \0 020  \0 016  \0
00002f0  \0  \0 001  \0  \0  \t 220 005 016  \0  \0  \0  \0  \0  \0  \0
0000300 001  \0  \0  \0  \0  \0  \0  \0 001  \0  \0  \0 024  \0  \0  \0
0000310   p  \0  \0  \0 002  \0  \0  \0  \b  \0  \0  \0   ?  \0  \0  \0
0000320 003  \0  \0  \0 005  \0  \0  \0   ?  \0  \0  \0 004  \0  \0  \0
0000330 001  \0  \0  \0 034 001  \0  \0 005  \0  \0  \0 005  \0  \0  \0
0000340   $ 001  \0  \0 006  \0  \0  \0 001  \0  \0  \0   L 001  \0  \0
0000350 002      \0  \0 024  \0  \0  \0   l 001  \0  \0 001 020  \0  \0
0000360 002  \0  \0  \0   p 002  \0  \0 003 020  \0  \0 002  \0  \0  \0
0000370 200 002  \0  \0 003      \0  \0 001  \0  \0  \0 210 002  \0  \0
0000380 001      \0  \0 001  \0  \0  \0 220 002  \0  \0  \0      \0  \0
0000390 001  \0  \0  \0   ? 002  \0  \0  \0 020  \0  \0 001  \0  \0  \0
00003a0   ? 002  \0  \0                                                
00003a4

我們還可以用-C顯示16進制和ASCII碼

hexdump -C classes.dex

00000000  64 65 78 0a 30 33 35 00  12 8f b1 77 7a e9 19 91  |dex.035....wz...|
00000010  f2 0c ff ce a0 ce aa cd  8f 9d 80 7a ac 18 49 bf  |...........z..I.|
00000020  a4 03 00 00 70 00 00 00  78 56 34 12 00 00 00 00  |....p...xV4.....|
00000030  00 00 00 00 f8 02 00 00  14 00 00 00 70 00 00 00  |............p...|
00000040  08 00 00 00 c0 00 00 00  05 00 00 00 e0 00 00 00  |................|
00000050  01 00 00 00 1c 01 00 00  05 00 00 00 24 01 00 00  |............$...|
00000060  01 00 00 00 4c 01 00 00  38 02 00 00 6c 01 00 00  |....L...8...l...|
00000070  6c 01 00 00 74 01 00 00  81 01 00 00 84 01 00 00  |l...t...........|
00000080  92 01 00 00 96 01 00 00  ad 01 00 00 c1 01 00 00  |................|
00000090  d5 01 00 00 f0 01 00 00  04 02 00 00 07 02 00 00  |................|
000000a0  0b 02 00 00 20 02 00 00  28 02 00 00 2e 02 00 00  |.... ...(.......|
000000b0  34 02 00 00 39 02 00 00  42 02 00 00 4c 02 00 00  |4...9...B...L...|
000000c0  03 00 00 00 05 00 00 00  06 00 00 00 07 00 00 00  |................|
000000d0  08 00 00 00 09 00 00 00  0a 00 00 00 0c 00 00 00  |................|
000000e0  02 00 00 00 03 00 00 00  00 00 00 00 04 00 00 00  |................|
000000f0  04 00 00 00 78 02 00 00  0a 00 00 00 06 00 00 00  |....x...........|
00000100  00 00 00 00 0b 00 00 00  06 00 00 00 78 02 00 00  |............x...|
00000110  0b 00 00 00 06 00 00 00  70 02 00 00 05 00 01 00  |........p.......|
00000120  10 00 00 00 00 00 04 00  0f 00 00 00 01 00 03 00  |................|
00000130  11 00 00 00 04 00 02 00  00 00 00 00 04 00 01 00  |................|
00000140  0d 00 00 00 04 00 00 00  12 00 00 00 00 00 00 00  |................|
00000150  01 00 00 00 02 00 00 00  00 00 00 00 ff ff ff ff  |................|
00000160  00 00 00 00 f0 02 00 00  00 00 00 00 06 3c 69 6e  |...............Hello World|
00000180  00 01 4c 00 0c 4c 48 65  6c 6c 6f 57 6f 72 6c 64  |..L..LHelloWorld|
00000190  3b 00 02 4c 4c 00 15 4c  6a 61 76 61 2f 69 6f 2f  |;..LL..Ljava/io/|
000001a0  50 72 69 6e 74 53 74 72  65 61 6d 3b 00 12 4c 6a  |PrintStream;..Lj|
000001b0  61 76 61 2f 6c 61 6e 67  2f 4f 62 6a 65 63 74 3b  |ava/lang/Object;|
000001c0  00 12 4c 6a 61 76 61 2f  6c 61 6e 67 2f 53 74 72  |..Ljava/lang/Str|
000001d0  69 6e 67 3b 00 19 4c 6a  61 76 61 2f 6c 61 6e 67  |ing;..Ljava/lang|
000001e0  2f 53 74 72 69 6e 67 42  75 69 6c 64 65 72 3b 00  |/StringBuilder;.|
000001f0  12 4c 6a 61 76 61 2f 6c  61 6e 67 2f 53 79 73 74  |.Ljava/lang/Syst|
00000200  65 6d 3b 00 01 56 00 02  56 4c 00 13 5b 4c 6a 61  |em;..V..VL..[Lja|
00000210  76 61 2f 6c 61 6e 67 2f  53 74 72 69 6e 67 3b 00  |va/lang/String;.|
00000220  06 61 70 70 65 6e 64 00  04 61 72 67 73 00 04 6d  |.append..args..m|
00000230  61 69 6e 00 03 6f 75 74  00 07 70 72 69 6e 74 6c  |ain..out..printl|
00000240  6e 00 08 74 6f 53 74 72  69 6e 67 00 0e e8 bf 99  |n..toString.....|
00000250  e6 98 af e4 b8 80 e4 b8  aa e6 89 8b e5 86 99 e7  |................|
00000260  9a 84 73 6d 61 6c 69 e5  ae 9e e4 be 8b 00 00 00  |..smali.........|
00000270  01 00 00 00 07 00 00 00  01 00 00 00 03 00 00 00  |................|
00000280  00 00 00 00 00 00 00 00  00 01 0f 07 00 00 00 00  |................|
00000290  0b 00 01 00 02 00 00 00  88 02 00 00 28 00 00 00  |............(...|
000002a0  62 00 00 00 00 00 00 00  00 00 12 32 13 03 ff ff  |b..........2....|
000002b0  18 04 00 00 01 00 00 00  00 00 1c 05 03 00 01 26  |...............&|
000002c0  22 07 04 00 70 10 02 00  07 00 1a 08 13 00 6e 20  |"...p.........n |
000002d0  03 00 87 00 0c 07 6e 10  04 00 07 00 0c 09 6e 20  |......n.......n |
000002e0  01 00 90 00 1a 01 01 00  6e 20 01 00 10 00 0e 00  |........n ......|
000002f0  00 00 01 00 00 09 90 05  0e 00 00 00 00 00 00 00  |................|
00000300  01 00 00 00 00 00 00 00  01 00 00 00 14 00 00 00  |................|
00000310  70 00 00 00 02 00 00 00  08 00 00 00 c0 00 00 00  |p...............|
00000320  03 00 00 00 05 00 00 00  e0 00 00 00 04 00 00 00  |................|
00000330  01 00 00 00 1c 01 00 00  05 00 00 00 05 00 00 00  |................|
00000340  24 01 00 00 06 00 00 00  01 00 00 00 4c 01 00 00  |$...........L...|
00000350  02 20 00 00 14 00 00 00  6c 01 00 00 01 10 00 00  |. ......l.......|
00000360  02 00 00 00 70 02 00 00  03 10 00 00 02 00 00 00  |....p...........|
00000370  80 02 00 00 03 20 00 00  01 00 00 00 88 02 00 00  |..... ..........|
00000380  01 20 00 00 01 00 00 00  90 02 00 00 00 20 00 00  |. ........... ..|
00000390  01 00 00 00 f0 02 00 00  00 10 00 00 01 00 00 00  |................|
000003a0  f8 02 00 00                                       |....|
000003a4

magic

標識一個有效的dex文件,他的固定值為:64 65 78 0a 30 33 35 00,轉換為字符串為dex.035.
在電子取證中也稱“文件簽名”

checksum

他是整個頭部的校驗和。它被用來校驗頭部是否損壞

signature

file_size

記錄包括dexHeader在內的整個dex文件大小,用來計算偏移和方便定位某區段(section),他也有諸如唯一的標識dex,因為他是dex文件中計算sha-1區段的一個組成部分

header_size

存放整個DexHeadeer結構體的長度,它也可用來計算下一個區段在文件中的起始位置,目前值為0x70

endian_tag

指定dex運行環境的CPU字節序,存放的是一個固定值,所有dex文件都一樣的,值為:78 56 34 12,0x12345678,表示默認采用little-endian字節序

link_size

link_off

當多個class文件被編譯到一個dex文件是,他們會用到link_size和link_off,通常為0

可以看到上面的,link_off:為00 00 00 00

map_off

他指定了dexMapList結構的文件偏移量

string_ids_size

是指string存放區段的大小,用來計算string區段起始位置-相對於dex文件加載基地址的偏移量

string_ids_off存放string區段的實際偏移量,單位字節。他可以幫助編譯器和虛擬機直接跳到這個區段,而不必從前讀到後,一直讀取到該位置。
type,prototype,method,class,data id的大小(size)和偏移量(offset)和string的作用一樣

每個字符串都對應一個DexStringId數據結構,大小為4B,同時虛擬機可以通過頭文件中的string_ids_size知道當前dex文件中字符串的總數,也就是string_ids區域中DexStringId數據結構的總數,所以虛擬機可以通過簡單的乘法運算即可實現對字符串資源的索引,也可以根據kDexTypeStringIdItem獲取字符串

我們舉個例子來根據header裡面的字符串信息索引字符串,還是以上面的classes.dex文件來分析:

根據stringIdsSize找到有多少個DexStringId(也就是有多少個字符串):

0x38:0x14,說明有20個字符串

根據stringIdsOff查看DexStringId的偏移量:

0x3c:0x70,說明DexStringId的開始位置在0x70

讀取4個字節:6c 01 00 00,轉為地址為0x16c,這就是第一個字符串的位置
在讀取4個字節:74 01 00 00,0x174

分別獲取這個兩個位置的字符串:

06 3c 69 6e 69 74 3e 00:值為\0,其中06表示後面有6個字符(不包括\0)
0b 48 65 6c 6c 6f 20 57 6f 72 6c 64:值為Hello World\0,0b表示有11個字符

我們發現每個字符串是使用“\0”分割的

首先他的開始位置0x70,我們根據stringIdsSize的值得知接下來有20個字符,首先我們計算DexStringId的地址截止到:0x70+0x14(20)*4=0xc0(不包括0xc0)

先獲取DexStringId,然後在獲取地址位置的值

DexStringId偏移 String偏移 0x70 0x16c 74 174 Hello World 78 181 L 7c 184 LHelloWorld; 80 192 LL 84 196 Ljava/io/PrintStream; 88 1ad Ljava/lang/Object; 8c 1c1 Ljava/lang/String; 90 1d5 Ljava/lang/StringBuilder; 94 1f0 Ljava/lang/System; 98 204 V 9c 207 VL a0 20b [Ljava/lang/String; a4 220 append a8 228 args ac 22e main b0 234 out b4 239 println b8 242 toString bc 24c  

上面的字符串並非普通的ASCII字符串,他們是由MUTF-8編碼來表示的,更詳細的介紹參考這篇文章

dex文件結構分析

我們采用前面的classes.dex文件作為演示對象

dalvik虛擬機解析dex文件的內容,最終將其映射成DexMapList數據結構,DexHeader中的mapOff字段指定了DexMapList結構在dex在文件中的偏移,他的申明如下:

/dalvik/libdex/DexFile.h

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

其中size字段表示dex接來下有多少個DexMapItem結構

struct DexMapItem {
    u2 type;              /* kDexType開頭的類型 */
    u2 unused;            /*未使用,用於字節對齊 */
    u4 size;              /* 類型的個數 */
    u4 offset;            /* 類型的文件偏移 */
};

type字段為一個枚舉常量,可以通過類型名稱很容易判斷他的具體類型:

enum {
    kDexTypeHeaderItem               = 0x0,
    kDexTypeStringIdItem             = 0x1,
    kDexTypeTypeIdItem               = 0x2,
    kDexTypeProtoIdItem              = 0x3,
    kDexTypeFieldIdItem              = 0x4,
    kDexTypeMethodIdItem             = 0x5,
    kDexTypeClassDefItem             = 0x6,
    kDexTypeMapList                  = 0x0,
    kDexTypeTypeList                 = 0x1,
    kDexTypeAnnotationSetRefList     = 0x2,
    kDexTypeAnnotationSetItem        = 0x3,
    kDexTypeClassDataItem            = 0x0,
    kDexTypeCodeItem                 = 0x1,
    kDexTypeStringDataItem           = 0x2,
    kDexTypeDebugInfoItem            = 0x3,
    kDexTypeAnnotationItem           = 0x4,
    kDexTypeEncodedArrayItem         = 0x5,
    kDexTypeAnnotationsDirectoryItem = 0x6,
};

這裡我們以上面的clsses.dex來分析,DexHeader結構的mapOff字段為f8 02 00 00,根據小端序,他的值為0x2f8,讀取出的雙字值為0e 00 00 00(0x0e),表示接下來有14個DexMapItem結構,接著在讀取0x2fc值為:0x00表示這個DexMapItem類型是kDexTypeHeaderItem,在讀取0x2fe值為:0x00這個字段沒有使用。在讀取0x300值為:01 00 00 00表示有一個,在0x304讀取:00 00 00 00表示偏移為0x0

根據上面的規則我們整理除了14個Item

類型 個數 偏移 kDexTypeHeaderItem 0x1 0x0 kDexTypeStringIdItem 0x14 0x70 kDexTypeTypeIdItem 0x8 0xc0 kDexTypeProtoIdItem 0x5 0xe0 kDexTypeFieldIdItem 0x1 0x11c kDexTypeMethodIdItem 0x5 0x124 kDexTypeClassDefItem 0x1 0x14c kDexTypeStringDataItem 0x14 0x16c kDexTypeTypeList 0x2 0x270 kDexTypeAnnotationSetItem 0x2 0x280 kDexTypeDebugsssInfoItem 0x1 0x288 kDexTypeCodeItem 0x1 0x290 kDexTypeClassDataItem 0x1 0x2f0 kDexTypeMapList 0x1 0x2f8

對比文件我們發下DexHeader就是kDexTypeHeaderItem描述的結構,他占用了文件前0x70個字節空間,接下來的kDexTypeStringIdItem~kDexTypeClassDefItem與DexHeader中對應字段值是一樣的

kDexTypeStringIdItem

對應DexHeader中的stringIdsSize與stringIdsOff字段,表示從0x70位置起有連續0x14個DexStringId:

c++
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};

他只有一個stringDataOff字段,指向字符串數據的偏移位置,開始地址為0x70+(14*4)=0xc0,所以我們最後一個dexStringId的偏移為:0xbc,我們根據此信息整理了所以字符串:

dexStringId偏移 真實字符串偏移 字符串 索引 0x70 0x16c 0x0 74 174 Hello World 1 78 181 L 2 7c 184 LHelloWorld; 3 80 192 LL 4 84 196 Ljava/io/PrintStream; 5 88 1ad Ljava/lang/Object; 6 8c 1c1 Ljava/lang/String; 7 90 1d5 Ljava/lang/StringBuilder; 8 94 1f0 Ljava/lang/System; 9 98 204 V a 9c 207 VL b a0 20b [Ljava/lang/String; c a4 220 append d a8 282 args e ac 22e main f b0 234 out 10 b4 239 println 11 b8 242 toString 12 bc 24c 這是一個手寫的smali實例 13

kDexTypeTypeIdItem

他對應DexHeader中的typeIdsSize和typeIdsOff字段,指向的結構體為:

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

對應的字符串代表具體的類型,我們根據上面字段可知:從0xc0起有0x8個DexTypeId結構:

類型索引 字符串索引 字符串 DexTypeId偏移 0 0x3 LHelloWorld; 0xc0 1 0x5 Ljava/io/PrintStream; 0xc4 2 0x6 Ljava/lang/Object; 0xc8 3 0x7 Ljava/lang/String; 0xcc 4 0x8 Ljava/lang/StringBuilder; 0xd0 5 0x9 Ljava/lang/System; 0xd4 6 0xa L 0xd8 7 0xc [Ljava/lang/String; 0xdc

kDexTypeProtoIdItem

對應DexHeader中的protoIdsSize與protoIdsOff字段,聲明如果:

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

他是一個方法的聲明結構體,shortyIdx為方法聲明字符串,returnTypeIdx為方法返回類型字符串,parametersOff指向一個DexTypeList的結構體存放了方法的參數列表

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

DexTypeItem聲明:

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

根據上面的信息我們得知從0xe0開始有0x5個DexProtoId對象

索引 方法聲明 返回類型 參數列表 paramOff偏移 0 L Ljava/lang/String; 無參數 0 1 LL Ljava/lang/StringBuilder; Ljava/lang/String; 0x278 2 V L 無參數   3 VL L Ljava/lang/String; 0x278 4 VL L [Ljava/lang/String; 0x270

kDexTypeFieldIdItem

對應DexHeader中的fieldIdsSize和fieldIdsOff字段,指向DexFieldId結構體

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

可以看見這個結構的數據全部是索引信息,指明了字段所在的類,字段類型,字段名,通過上面的信息我們發現從0x11c有一個kDexTypeFieldIdItem

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

kDexTypeMethodIdItem

它對應DexHeader中的methodIdsSize與methodIdsOff字段,指向的結構體DexMethodId

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

數據也是索引,指明了方法所在的類,方法聲明和方法名。從0x124有0x5個kDexTypeMethodIdItem

類類型 方法聲明 方法名 LHelloWorld; VL main Ljava/io/PrintStream; VL println Ljava/lang/StringBuilder; V Ljava/lang/StringBuilder; LL append Ljava/lang/StringBuilder; L toString

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如果類含有接口聲明或實現,他就會指向一個DexTypeList結構,否則為0

sourceFileIdx是類所在源文件名稱

annotationsOff字段指向注解目錄結構,根據類型不同有注解類,注解方法,注解字段,注解參數。如果沒有注解,值為0

classDataOff指向DexClassData結構,是類的數據部分

staticValuesOff記錄了類中的靜態數據

DexClassData

聲明在DexClass.h中

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] */
};

registersSize指令了方法使用的寄存器個數,對應smali中的.locals 2

insSize指定了方法的參數個數,對應.param p1, “noteId” # Ljava/lang/String;

如果一個方法使用5個寄存器,其中2有兩個參數寄存器,而該方法調用另一個方法使用了20個寄存器,那麼虛擬機在分配該方法寄存器是會在分配20個寄存器

triesSize指定了方法中Try/Catch格式

debugInfoOff指向調試信息偏移,如果有,解析函數為dexDecodoDebugInfo在DexDebugInfo.cpp文件

insnsSize接下來指令個數

insns真正的代碼部分

根據上面的信息,我們發現從0x14c有0x1個kDexTypeClassDefItem:

第一個字段值為0x0,表示對應DexType中的索引為0,值為LHelloWorld;,表示類名為HelloWorld

第二個字段值為0x1表示訪問標識符為ACC_PUBLIC

第三個字段值0x2表示父類,表示指向DexType的索引,值為Ljava/lang/Object;,表示父類為java/lang/Object;

第四個字段值0x0,表示沒有接口

第五個字段值

第六個字段值0x0表示沒有注解

第七個字段值0x2f0表示DexClassData的偏移

第八個字段值0x0表示沒有靜態值

我們繼續分析DexClassData

DexClassDataHeader為4個uleb128數據類型,從0x2f0開始值為:0,0,1,0,表示靜態字段為0個,實例字段為0個,1個直接方法,0個虛方法

由於沒有靜態字段,實例字段,虛方法,所以我們直接分析DexMethod

從0x2f4開始為一個直接方法相關數據,第一個字段為0,指向的DexMethod列表第0個,也就是main方法。第二個值09,表示public+static,具體的看下面表:

現在來說下accessFlags在字節碼中的計算方式:
access_flags的計算公式為:access_flags = flagA | flagB | flagB …

標志名稱 標志值 含義 ACC_PUBLIC 0x0001 public ACC_PRIVATE 0x0002 private ACC_PROTECTED 0x0004 protected ACC_STATIC 0x0008 static ACC_FINAL 0x0010 final ACC_VOLATILE 0x0040 volatile ACC_TRANSIENT 0x0080 transient ACC_SYNTHETIC 0x1000 是否是編譯器自動生成的 ACC_ENUM 0x4000 enum

第三個字段為90 05,值為0x290,我們從0x290開始分析DexCode,值為:

0b 00=11

01 00=1

02 00=2

00 00=0

表示使用了11個寄存器,1個參數寄存器,調用其他方法使用了2個寄存器,沒有Try/Catch

88 02 00 00=0x288,debugInfoOff

28 00 00 00=0x28,insnsSize,指令個數,以2字節為單位

我們先讀取一個兩個字節6200,查看dalvik bytecode發現為sget-object,指令格式為21c,查詢instruction formats可以看到指令格式為:

AA|op BBBB

可以看出他需要兩個16位,21C對應有以下幾種格式:

op vAA, type@BBBB check-cast op vAA, field@BBBB const-class op vAA, string@BBBB const-string

由於我們的指令是sget,所有這條指令格式為op vAA, field@BBBB

在讀取兩個字節0000,表示在字段索引0,所以這條指令為

sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

繼續從0x2a4分析:

00 00,查看bytecode為10x,在查詢指令格式為:

??|op 10x op,可以看出只需要兩個字節,所以這條指令為:nop

00 00:nop

00 00:nop

12 32:op=12,A=2,B=3查詢字節碼代碼格式,const/4 vA, #+B,格式為11n,最終翻譯為:const/4 v2, 0x3

13 03:op=13,const/16 vAA, #+BBBB,格式21s,格式為AA|op BBBB,所以需要兩個字節,在讀取兩個字節,ff ff,最終指令格式為:const/16 v3, -0x1

剩下的指令可以按照上面的步驟翻譯完

18 04:

const-wide vAA, #+BBBBBBBBBBBBBBBB

AA|op
BBBBlo BBBB BBBB BBBBhi

0000 0100 0000 0000
每兩位調換位置:
0000 0010 0000 0000
從低位到高位排列
0000 0000 0010 0000

最後這條指令為:

const-wide v4, 0x100000

如果我的文章對來帶來的幫助或者有不明白的地方,可加QQ群:129961195,大家一起交流,下一篇文章我們講解odex文件格式

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