編輯:關於Android編程
他是Android系統的可執行文件,包含應用程序的全部操作指令以及運行時數據。
由於dalvik是一種針對嵌入式設備而特殊設計的java虛擬機,所以dex文件與標准的class文件在結構設計上有著本質的區別
當java程序編譯成class後,還需要使用dx工具將所有的class文件整合到一個dex文件,目的是其中各個類能夠共享數據,在一定程度上降低了冗余,同時也是文件結構更加經湊,實驗表明,dex文件是傳統jar文件大小的50%左右
可以看見:
dex將原來class每個文件都有的共有信息合成一體,這樣減少了class的冗余
其中u1~u8很好理解,不理解的可以參考這裡,表示1到8個字節的無符號數,後面三個是dex特有的數據類型。
首先從宏觀上來說dex的文件結果很簡單,實際上是由多個不同結構的數據體以首尾相接的方式拼接而成。如下圖:
/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文件中
簡單記錄了dex文件的一些基本信息,以及大致的數據分布。長度固定為0x70,其中每一項信息所占用的內存空間也是固定的,好處是虛擬機在處理dex時不用考慮dex文件的多樣性
/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
標識一個有效的dex文件,他的固定值為:64 65 78 0a 30 33 35 00,轉換為字符串為dex.035.
在電子取證中也稱“文件簽名”
他是整個頭部的校驗和。它被用來校驗頭部是否損壞
記錄包括dexHeader在內的整個dex文件大小,用來計算偏移和方便定位某區段(section),他也有諸如唯一的標識dex,因為他是dex文件中計算sha-1區段的一個組成部分
存放整個DexHeadeer結構體的長度,它也可用來計算下一個區段在文件中的起始位置,目前值為0x70
指定dex運行環境的CPU字節序,存放的是一個固定值,所有dex文件都一樣的,值為:78 56 34 12,0x12345678,表示默認采用little-endian字節序
當多個class文件被編譯到一個dex文件是,他們會用到link_size和link_off,通常為0
可以看到上面的,link_off:為00 00 00 00
他指定了dexMapList結構的文件偏移量
是指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:值為
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文件格式
本文均屬自己閱讀源碼的點滴總結,轉賬請注明出處謝謝。 歡迎和大家交流。qq:1037701636 email:[email protected] Androi
小米4、紅米2、小米Note的手機用戶可能經常會遇到“請勿遮擋橙色區域”的提示,這其實是小米手機的防誤觸功能的提示,以保證手機在口袋
Canvas 即“畫布”的意思,在Android中用其來進行2D繪畫。在使用canvas來進行繪圖時,一般都會自定義一個View來重寫
本實例通過TimePickerDialog時間選擇對話框讓用戶設置鬧鐘,並通過AlarmManager全局定時器在指定的時間啟動鬧鐘Activity 。 程序運行效果圖