Android 虚拟机 — .dex 文件格式
前言 关于 .dex 文件格式,网址 https://source.android.com/devices/tech/dalvik/dex-format 中有极为详尽的描述,讲述的非常清楚,还可以把它当作工具来参考。但只是大体略一遍,印象不会太深刻,因此,我自己写了一个例子拿来分析一下,并给出一些自己的整理和总结。示例public class Hello {private static
前言
关于 .dex 文件格式,网址 https://source.android.com/devices/tech/dalvik/dex-format 中有极为详尽的描述,讲述的非常清楚,还可以把它当作工具来参考。但只是大体略一遍,印象不会太深刻,因此,我自己写了一个例子拿来分析一下,并给出一些自己的整理和总结。
示例
public class Hello {
private static final String Hello_DEX = "Hello! My Dex!";
public static void main(String[] args) {
System.out.println(Hello_DEX + "\n");
}
}
- 编译:javac Hello.java
- 执行:java Hello
- 生成 dex 文件:dx –dex –output=Hello.dex Hello.class
- 运行 push 进手机的 dex 文件:
- adb shell
- dalvikvm -cp /storage/emulated/0/Hello.dex Hello
- 查看二进制 dex 文件:hexdump -C Hello.dex
00000000 64 65 78 0a 30 33 35 00 d5 68 ac d8 fd 4c 28 8c |dex.035..h...L(.|
00000010 61 91 7d 47 00 f8 c2 d7 e6 d4 f2 f2 fa 3d f3 58 |a.}G.........=.X|
00000020 20 03 00 00 70 00 00 00 78 56 34 12 00 00 00 00 | ...p...xV4.....|
00000030 00 00 00 00 74 02 00 00 10 00 00 00 70 00 00 00 |....t.......p...|
00000040 07 00 00 00 b0 00 00 00 03 00 00 00 cc 00 00 00 |................|
00000050 02 00 00 00 f0 00 00 00 04 00 00 00 00 01 00 00 |................|
00000060 01 00 00 00 20 01 00 00 e0 01 00 00 40 01 00 00 |.... .......@...|
00000070 86 01 00 00 8e 01 00 00 9e 01 00 00 af 01 00 00 |................|
00000080 bb 01 00 00 c6 01 00 00 cf 01 00 00 e6 01 00 00 |................|
00000090 fa 01 00 00 0e 02 00 00 22 02 00 00 25 02 00 00 |........"...%...|
000000a0 29 02 00 00 3e 02 00 00 44 02 00 00 49 02 00 00 |)...>...D...I...|
000000b0 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 |................|
000000c0 09 00 00 00 0a 00 00 00 0c 00 00 00 0a 00 00 00 |................|
000000d0 05 00 00 00 00 00 00 00 0b 00 00 00 05 00 00 00 |................|
000000e0 78 01 00 00 0b 00 00 00 05 00 00 00 80 01 00 00 |x...............|
000000f0 00 00 03 00 04 00 00 00 04 00 01 00 0e 00 00 00 |................|
00000100 00 00 00 00 00 00 00 00 00 00 02 00 0d 00 00 00 |................|
00000110 01 00 01 00 0f 00 00 00 02 00 00 00 00 00 00 00 |................|
00000120 00 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 |................|
00000130 03 00 00 00 00 00 00 00 61 02 00 00 5e 02 00 00 |........a...^...|
00000140 01 00 01 00 01 00 00 00 52 02 00 00 04 00 00 00 |........R.......|
00000150 70 10 03 00 00 00 0e 00 03 00 01 00 02 00 00 00 |p...............|
00000160 57 02 00 00 08 00 00 00 62 00 01 00 1a 01 02 00 |W.......b.......|
00000170 6e 20 02 00 10 00 0e 00 01 00 00 00 03 00 00 00 |n ..............|
00000180 01 00 00 00 06 00 06 3c 69 6e 69 74 3e 00 0e 48 |.......<init>..H|
00000190 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 00 0f 48 |ello! My Dex!..H|
000001a0 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 0a 00 0a |ello! My Dex!...|
000001b0 48 65 6c 6c 6f 2e 6a 61 76 61 00 09 48 65 6c 6c |Hello.java..Hell|
000001c0 6f 5f 44 45 58 00 07 4c 48 65 6c 6c 6f 3b 00 15 |o_DEX..LHello;..|
000001d0 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 |Ljava/io/PrintSt|
000001e0 72 65 61 6d 3b 00 12 4c 6a 61 76 61 2f 6c 61 6e |ream;..Ljava/lan|
000001f0 67 2f 4f 62 6a 65 63 74 3b 00 12 4c 6a 61 76 61 |g/Object;..Ljava|
00000200 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 12 4c |/lang/String;..L|
00000210 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d |java/lang/System|
00000220 3b 00 01 56 00 02 56 4c 00 13 5b 4c 6a 61 76 61 |;..V..VL..[Ljava|
00000230 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 04 6d |/lang/String;..m|
00000240 61 69 6e 00 03 6f 75 74 00 07 70 72 69 6e 74 6c |ain..out..printl|
00000250 6e 00 01 00 07 0e 00 05 01 00 07 0e 78 00 01 17 |n...........x...|
00000260 01 01 00 02 00 00 1a 00 81 80 04 c0 02 01 09 d8 |................|
00000270 02 00 00 00 0e 00 00 00 00 00 00 00 01 00 00 00 |................|
00000280 00 00 00 00 01 00 00 00 10 00 00 00 70 00 00 00 |............p...|
00000290 02 00 00 00 07 00 00 00 b0 00 00 00 03 00 00 00 |................|
000002a0 03 00 00 00 cc 00 00 00 04 00 00 00 02 00 00 00 |................|
000002b0 f0 00 00 00 05 00 00 00 04 00 00 00 00 01 00 00 |................|
000002c0 06 00 00 00 01 00 00 00 20 01 00 00 01 20 00 00 |........ .... ..|
000002d0 02 00 00 00 40 01 00 00 01 10 00 00 02 00 00 00 |....@...........|
000002e0 78 01 00 00 02 20 00 00 10 00 00 00 86 01 00 00 |x.... ..........|
000002f0 03 20 00 00 02 00 00 00 52 02 00 00 05 20 00 00 |. ......R.... ..|
00000300 01 00 00 00 5e 02 00 00 00 20 00 00 01 00 00 00 |....^.... ......|
00000310 61 02 00 00 00 10 00 00 01 00 00 00 74 02 00 00 |a...........t...|
00000320
一、.dex 文件结构
1.1 DexHeader (header_item)
Type | 偏移 | size | 数据 | value | 说明 |
---|---|---|---|---|---|
magic | 0 | 8 | 64 65 78 0a 30 33 35 00 | “dex\n035\0” | 魔法值。 |
checksum | 8 | 4 | d5 68 ac d8 | 文件剩余内容(除 magic 和此字段之外的所有内容)的 adler32 校验和;用于检测文件损坏情况 | |
siganature | 12 | 20 | 文件剩余内容(除 magic、checksum 和此字段之外的所有内容)的 SHA-1 签名(哈希);用于对文件进行唯一标识 | ||
file_size | 32 | 4 | 20 03 00 00 | 0x0320 | 整个文件(包括标头)的大小,以字节为单位 |
header_size | 36 | 4 | 70 00 00 00 | 0x70 | 标头(整个区段)的大小,以字节为单位。 |
endian_tag | 40 | 4 | 78 56 34 12 | 0x12345678 | 字节序标记。 |
link_size | 44 | 8 | 00 00 00 00 | 0x00 | 链接区段的大小;如果此文件未进行静态链接,则该值为 0 |
link_off | 48 | 4 | 00 00 00 00 | 0x00 | 该偏移量(如果为非零值)应该是到 link_data 区段的偏移量。 |
map_off | 52 | 4 | 74 02 00 00 | 0x0274 | |
string_ids_size | 56 | 4 | 10 00 00 00 | 0x10 | 字符串标识符列表中的字符串数量 |
string_ids_off | 60 | 4 | 70 00 00 00 | 0X70 | 从文件开头到字符串标识符列表的偏移量 |
type_ids_size | 64 | 4 | 07 00 00 00 | 0x07 | 类型标识符列表中的元素数量,最多为 65535 |
type_ids_off | 68 | 4 | b0 00 00 00 | 0xb0 | 从文件开头到类型标识符列表的偏移量 |
proto_ids_size | 72 | 4 | 03 00 00 00 | 0x03 | 原型标识符列表中的元素数量,最多为 65535 |
proto_ids_off | 76 | 4 | cc 00 00 00 | 0xcc | 从文件开头到原型标识符列表的偏移量 |
field_ids_size | 80 | 4 | 02 00 00 00 | 0x02 | 字段标识符列表中的元素数量 |
field_ids_off | 84 | 4 | f0 00 00 00 | 0xf0 | 从文件开头到字段标识符列表的偏移量 |
method_ids_size | 88 | 4 | 04 00 00 00 | 0x04 | 方法标识符列表中的元素数量 |
method_ids_off | 92 | 4 | 00 01 00 00 | 0x0100 | 从文件开头到方法标识符列表的偏移量 |
class_defs_size | 96 | 4 | 01 00 00 00 | 0x01 | 类定义列表中的元素数量 |
class_defs_off | 100 | 4 | 20 01 00 00 | 0x0120 | 从文件开头到类定义列表的偏移量 |
data_size | 104 | 4 | e0 01 00 00 | 0x01e0 | data 区段的大小(以字节为单位)。该数值必须是 sizeof(uint) 的偶数倍 |
data_off | 108 | 4 | 40 01 00 00 | 0x0140 | 从文件开头到 data 区段开头的偏移量 |
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;
};
1.1.1 DEX_FILE_MAGIC (magic)
主要作用是版本标识,这里:
ubyte[8] DEX_FILE_MAGIC = { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 }
= "dex\n035\0"
- Android 7.0 版本中新增了对 037 版格式的支持。在 037 版本之前,大多数 Android 版本都使用过 035 版格式。035 版与 037 版之间的唯一区别是,是否添加默认方法以及是否调整 invoke。
- Android 8.0 版本中新增了对 038 版格式的支持。038 版本中添加了新字节码(invoke-polymorphic 和 invoke-custom)和用于方法句柄的数据。
1.1.2 ENDIAN_CONSTANT (endianTag)
标准的 .dex 格式采用小端字节序,即 endian_tag 为 ENDIAN_CONSTANT。如果实现遇到其 endian_tag 为 REVERSE_ENDIAN_CONSTANT(而非 ENDIAN_CONSTANT)的标头,则会识别该文件已从预期格式进行过字节交换。
uint ENDIAN_CONSTANT = 0x12345678;
uint REVERSE_ENDIAN_CONSTANT = 0x78563412;
1.2 .dex 索引区
1.2.1 string_ids
字符串标识符列表,格式:string_id_item[],列表中存储的就是一个个如下的数据结构,占用 4 bytes,数据 stringDataOff 指示的是每个 string_data_item 结构的偏移
/*
* Direct-mapped "string_id_item".
*/
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};
由 00000030 00 00 00 00 74 02 00 00 10 00 00 00 70 00 00 00
可知:
本 dex 文件共有 16 个 string_id_item,第一个的偏移是 0x70,数据如下:
00000070 86 01 00 00 8e 01 00 00 9e 01 00 00 af 01 00 00
|…………….|
00000080 bb 01 00 00 c6 01 00 00 cf 01 00 00 e6 01 00 00
|…………….|
00000090 fa 01 00 00 0e 02 00 00 22 02 00 00 25 02 00 00
|……..”…%…|
000000a0 29 02 00 00 3e 02 00 00 44 02 00 00 49 02 00 00
|)…>…D…I…|
string_data_item 的结构:(对齐:无字节对齐)
struct string_data_item
{
uleb128 utf16_size;
ubyte data;
}
string_data_item 的数据如下所示:
00000180 01 00 00 00 06 00 06 3c 69 6e 69 74 3e 00 0e 48
|……...H|
00000190 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 00 0f 48
|ello! My Dex!..H|
000001a0 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 0a 00 0a
|ello! My Dex!…|
000001b0 48 65 6c 6c 6f 2e 6a 61 76 61 00 09 48 65 6c 6c
|Hello.java..Hell|
000001c0 6f 5f 44 45 58 00 07 4c 48 65 6c 6c 6f 3b 00 15
|o_DEX..LHello;..|
000001d0 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74
|Ljava/io/PrintSt|
000001e0 72 65 61 6d 3b 00 12 4c 6a 61 76 61 2f 6c 61 6e
|ream;..Ljava/lan|
000001f0 67 2f 4f 62 6a 65 63 74 3b 00 12 4c 6a 61 76 61
|g/Object;..Ljava|
00000200 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 12 4c
|/lang/String;..L|
00000210 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d
|java/lang/System|
00000220 3b 00 01 56 00 02 56 4c 00 13 5b 4c 6a 61 76 61
|;..V..VL..[Ljava|
00000230 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 04 6d
|/lang/String;..m|
00000240 61 69 6e 00 03 6f 75 74 00 07 70 72 69 6e 74 6c
|ain..out..printl|
00000250 6e 00
01 00 07 0e 00 05 01 00 07 0e 78 00 01 17 |n………..x…|
Index | 偏移 | utf16_size (uleb128 / int) | data | String |
---|---|---|---|---|
0 | 0x0186 | 0x06/6 | 3c 69 6e 69 74 3e 00 | < init > |
1 | 0x018e | 0x0e/14 | 48 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 00 | Hello! My Dex! |
2 | 0x019e | 0x0f/15 | 48 65 6c 6c 6f 21 20 4d 79 20 44 65 78 21 0a 00 | Hello! My Dex!\n |
3 | 0x01af | 0x0a/10 | 48 65 6c 6c 6f 2e 6a 61 76 61 00 | Hello.java |
4 | 0x01bb | 0x09/9 | 48 65 6c 6c 6f 5f 44 45 58 00 | Hello_DEX |
5 | 0x01c6 | 0x07/7 | 4c 48 65 6c 6c 6f 3b 00 | LHello; |
6 | 0x01cf | 0x15/21 | 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 00 | Ljava/io/PrintStream; |
7 | 0x01e6 | 0x12/18 | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b 00 | Ljava/lang/Object; |
8 | 0x01fa | 0x12/18 | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 | Ljava/lang/String; |
9 | 0x020e | 0x12/18 | 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 3b 00 | Ljava/lang/System; |
10 | 0x0222 | 0x01/1 | 56 00 | V |
11 | 0x0225 | 0x02/2 | 56 4c 00 | VL |
12 | 0x0229 | 0x13/19 | 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 | [Ljava/lang/String; |
13 | 0x023e | 0x04/4 | 6d 61 69 6e 00 | main |
14 | 0x0244 | 0x03/3 | 6f 75 74 00 | out |
15 | 0x0249 | 0x07/7 | 70 72 69 6e 74 6c 6e 00 | println |
1.2.2 type_ids
类型标识符列表,格式:type_id_item[]
/*
* Direct-mapped "type_id_item".
*/
struct DexTypeId {
u4 descriptorIdx; /* index into stringIds list for type descriptor */
};
descriptorIdx 为对应 type 描述字符串的 stringId 的 index
00000040 07 00 00 00 b0 00 00 00
03 00 00 00 cc 00 00 00 |…………….|
由 dexheader 的数据可知,type_id_item 共有 7 个,从 0xb0 开始
000000b0 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00
|…………….|
000000c0 09 00 00 00 0a 00 00 00 0c 00 00 00
0a 00 00 00 |…………….|
Index | 值 | String |
---|---|---|
0 | 0x05 | LHello; |
1 | 0x06 | Ljava/io/PrintStream; |
2 | 0x07 | Ljava/lang/Object; |
3 | 0x08 | Ljava/lang/String; |
4 | 0x09 | Ljava/lang/System; |
5 | 0x0a | V |
6 | 0x0c | [Ljava/lang/String; |
1.2.3 proto_ids
方法原型标识符列表,格式:proto_id_item[],这些是此文件引用的所有原型的标识符。
/*
* Direct-mapped "proto_id_item".
*/
struct DexProtoId {
u4 shortyIdx; /* index into stringIds for shorty descriptor */
u4 returnTypeIdx; /* index into typeIds list for return type */
u4 parametersOff; /* file offset to type_list for parameter types */
};
- shortyIdx 为对应 shorty 描述字符串的 stringId 的 index
- returnTypeIdx 为对应 return type 的 typeId 的 index
- parametersOff 是 offset , 指向 method 原型的参数列表 type_list ; 若 method 没有参数 ,值为0 。参数列表的格式是 type_list ,结构从逻辑上如下描述 。size 表示参数的个数 ;type_idx 是对应参数的类型 ,它的值是一个 typeId 的 index 号 ,跟 returnTypeIdx 含义类似。
struct type_list
{
uint size;
ushort type_idx[size];
}
由上面可知每个 proto_id_item 占 12 bytes ,其共有 3 个,起始偏移为 0xcc
000000c0 09 00 00 00 0a 00 00 00 0c 00 00 00 0a 00 00 00
|…………….|
000000d0 05 00 00 00 00 00 00 00 0b 00 00 00 05 00 00 00
|…………….|
000000e0 78 01 00 00 0b 00 00 00 05 00 00 00 80 01 00 00
|x……………|
查找 0x0178 对应的参数 list:
00000170 6e 20 02 00 10 00 0e 00 01 00 00 00 03 00 00 00
|n …………..|
注意:因为 type_list 是 4 字节对齐的,所以这里这用了 8 个字节
由此可知其共有一个参数,为 Ljava/lang/String;
查找 0x0180 对应的参数 list:
00000180 01 00 00 00 06 00
06 3c 69 6e 69 74 3e 00 0e 48 |……...H|
由此可知其共有一个参数,为 [Ljava/lang/String;
Index | 值 | shortyIdx | returnTypeIdx | parametersOff |
---|---|---|---|---|
0 | 0x0a 0x05 0x00 | V | V | 无 |
1 | 0x0b 0x05 0x0178 | VL | V | Ljava/lang/String; |
2 | 0x0b 0x05 0x0180 | VL | V | [Ljava/lang/String; |
由此可知,其分别描述了 < init >、System.out.println() 和 public static void main()
1.2.4 field_ids
字段标识符列表,格式:field_id_item[],这些是此文件引用的所有字段(相当于成员变量?)的标识符(无论文件中是否已定义)。
/*
* Direct-mapped "field_id_item".
*/
struct DexFieldId {
u2 classIdx; /* index into typeIds list for defining class */
u2 typeIdx; /* index into typeIds for field type */
u4 nameIdx; /* index into stringIds for field name */
};
- classIdx: defining class 对应的 typeIds 的 index
- typeIdx: field type 对应的 typeIds 的 index
- nameIdx: field name 对应的 stringIds 的 index
由此可知,每个 field_id_item 占用 8 bytes,其共有 2 个,起始偏移为 0xf0
000000f0 00 00 03 00 04 00 00 00 04 00 01 00 0e 00 00 00
|…………….|
Index | classIdx typeIdx nameIdx | class_string | type_string | name_string |
---|---|---|---|---|
0 | 0x00 0x00 0x0004 | LHello; | Ljava/lang/String; | Hello_DEX |
1 | 0x04 0x01 0x000e | Ljava/lang/System; | Ljava/io/PrintStream; | out |
1.2.5 method_ids
方法标识符列表,格式:method_id_item[],这些是此文件引用的所有方法的标识符(无论文件中是否已定义)。
/*
* Direct-mapped "method_id_item".
*/
struct DexMethodId {
u2 classIdx; /* index into typeIds list for defining class */
u2 protoIdx; /* index into protoIds for method prototype */
u4 nameIdx; /* index into stringIds for method name */
};
由此可知,其结构基本同上,每个占用 8 bytes,共有 4 个,起始偏移为 0x0100
00000100 00 00 00 00 00 00 00 00 00 00 02 00 0d 00 00 00
|…………….|
00000110 01 00 01 00 0f 00 00 00 02 00 00 00 00 00 00 00
|…………….|
Index | classIdx protoIdx nameIdx | class_string | proto_string | name_string |
---|---|---|---|---|
0 | 0x00 0x00 0x0000 | LHello; | V()V | < init > |
1 | 0x00 0x02 0x000d | LHello; | VL([Ljava/lang/String;)V | main |
2 | 0x01 0x01 0x000f | Ljava/io/PrintStream; | VL(Ljava/lang/String;)V | println |
3 | 0x02 0x00 0x0000 | Ljava/lang/Object; | V()V | < init > |
1.2.6 class_defs
类定义列表,格式:class_def_item[],这些类必须进行排序,以便所指定类的超类和已实现的接口比引用类更早出现在该列表中。
/*
* Direct-mapped "class_def_item".
*/
struct DexClassDef {
u4 classIdx; /* index into typeIds for this class */
u4 accessFlags;
u4 superclassIdx; /* index into typeIds for superclass */
u4 interfacesOff; /* file offset to DexTypeList */
u4 sourceFileIdx; /* index into stringIds for source file name */
u4 annotationsOff; /* file offset to annotations_directory_item */
u4 classDataOff; /* file offset to class_data_item */
u4 staticValuesOff; /* file offset to DexEncodedArray */
};
由上可知每个 class_def_item 占用 32 bytes,其共有 1 个,起始偏移是 0x0120
00000120 00 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00
|…………….|
00000130 03 00 00 00 00 00 00 00 61 02 00 00 5e 02 00 00
|……..a…^…|
名称 | value | 说明 |
---|---|---|
classIdx | 0x00 | 是 class type 在 typeIds 区中的 index,即 LHello; |
accessFlags | 0x00000001 | 是类的访问标记(public、final 等)。 |
superclassIdx | 0x02 | 是 superclass type 在 typeIds 区中的 index,即 LHello; 的父类为 Ljavag/Object; |
interfacesOff | 0x00 | 从文件开头到接口列表的偏移量;如果没有接口,则该值为 0。该偏移量应该位于 data 区段,且其中的数据应采用 “type_list” 指定的格式。 |
sourceFileIdx | 0x03 | 是 source file name 在 stringIds 区中的 index,即 Hello.java |
annotationsOff | 0x00 | 从文件开头到此类的注释结构的偏移量;如果此类没有注释,则该值为 0。该偏移量(如果为非零值)应该位于 data 区段,且其中的数据应采用 “annotations_directory_item” 指定的格式 |
classDataOff | 0x0261 | 从文件开头到此项的关联类数据的偏移量;如果此类没有类数据,则该值为 0。 该偏移量(如果为非零值)应该位于 data 区段,且其中的数据应采用 “class_data_item” 指定的格式 |
staticValuesOff | 0x025e | 从文件开头到 static 字段的初始值列表的偏移量;如果没有该列表(并且所有 static 字段都将使用 0 或 null 进行初始化),则该值为 0。该偏移量应该位于 data 区段,且其中的数据应采用 “encoded_array_item” 指定的格式。 |
1.3 data 区
1.3.1 class_data_item
00000260 01 01 00 02 00 00 1a 00 81 80 04 c0 02 01 09 d8
|…………….|
00000270 02
00 00 00 0e 00 00 00 00 00 00 00 01 00 00 00 |…………….|
class_data_item 被 1.2.6 中的 classDataOff 指引,其结构如下所示:
名称 | 格式 | 说明 | 对应值 | 含义/value |
---|---|---|---|---|
static_fields_size | uleb128 | 此项中定义的静态字段的数量 | 0x01 | 1 |
instance_fields_size | uleb128 | 此项中定义的实例字段的数量 | 0x00 | 0 |
direct_methods_size | uleb128 | 此项中定义的实例字段的数量 | 0x02 | 2 |
virtual_methods_size | uleb128 | 此项中定义的实例字段的数量 | 0x00 | 0 |
static_fields | encoded_field[static_fields_size] | 定义的静态字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。 | 0x00 0x1a | private static final Hello_DEX (得出方式见后面) |
instance_fields | encoded_field[instance_fields_size] | 定义的实例字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。 | 无 | 无 |
direct_methods | encoded_method[direct_methods_size] | 定义的直接(static、private 或构造函数的任何一个)方法;以一系列编码元素的形式表示。 | 1. 0x00 0x818004 0xc002 2. 0x01 0x09 0xd802 | 1. LHello; 的 public 的构造函数方法 < init >,并且 code_off 为 0x140 2. LHello; 的 public static main() 方法,并且 code_off 为 0x158 |
virtual_methods | encoded_method[virtual_methods_size] | 定义的虚拟(非 static、private 或构造函数)方法;以一系列编码元素的形式表示。 | 无 | 无 |
看一下 encoded_field 和 encoded_method 的格式:
- encoded_field 格式
名称 | 格式 | 说明 |
---|---|---|
field_idx_diff | uleb128 | 此字段标识(包括名称和描述符)的 field_ids 列表中的索引;它会表示为与列表中前一个元素的索引之间的差值。列表中第一个元素的索引则直接表示出来。 |
access_flags | uleb128 | 字段的访问标记(public、final 等)。 |
所以可知:
static_fields 对应的值为 0x00 0x1a,即其对应 private static final Hello_DEX
- encoded_method 格式
名称 | 格式 | 说明 |
---|---|---|
method_idx_diff | uleb128 | 此方法标识(包括名称和描述符)的 method_ids 列表中的索引;它会表示为与列表中前一个元素的索引之间的差值。列表中第一个元素的索引则直接表示出来。 |
access_flags | uleb128 | 方法的访问标记(public、final 等)。 |
code_off | uleb128 | 从文件开头到此方法代码结构的偏移量;如果此方法是 abstract 或 native,则该值为 0。该偏移量应该是到 data 区段中某个位置的偏移量。数据格式由下文的“code_item”指定。 |
所以可知:
- direct_methods 中第一个元素对应的值为 0x00 0x818004 0xc002,即其对应 LHello; 的 public 的构造函数方法 < init >,并且 code_off 为 0x140
- direct_methods 中第二个元素对应的值为 0x01 0x09 0xd802,即其对应 LHello; 的 public static main() 方法,并且 code_off 为 0x158
1.3.2 code_item
引用自上面 encoded_method,以 4 个字节对齐。
名称 | 格式 | 说明 |
---|---|---|
registers_size | ushort | 此代码使用的寄存器数量 |
ins_size | ushort | 此代码所用方法的传入参数的字数 |
outs_size | ushort | 此代码进行方法调用所需的传出参数空间的字数 |
tries_size | ushort | 此实例的 try_item 数量。如果此值为非零值,则这些项会显示为 tries 数组(正好位于此实例中 insns 的后面)。 |
debug_info_off | uint | 从文件开头到此代码的调试信息(行号 + 局部变量信息)序列的偏移量;如果没有任何信息,则该值为 0。该偏移量(如果为非零值)应该是到 data 区段中某个位置的偏移量。数据格式由下文的“debug_info_item”指定。 |
insns_size | uint | 指令列表的大小(以 16 位代码单元为单位) |
insns | ushort[insns_size] | 字节码的实际数组。 |
padding | ushort(可选)= 0 | 使 tries 实现四字节对齐的两字节填充。只有 tries_size 为非零值且 insns_size 是奇数时,此元素才会存在。 |
tries | try_item[tries_size](可选) | 用于表示在代码中捕获异常的位置以及如何对异常进行处理的数组。该数组的元素在范围内不得重叠,且数值地址按照从低到高的顺序排列。只有 tries_size 为非零值时,此元素才会存在。 |
handlers | encoded_catch_handler_list(可选) | 用于表示“捕获类型列表和关联处理程序地址”的列表的字节。每个 try_item 都具有到此结构的分组偏移量。只有 tries_size 为非零值时,此元素才会存在。 |
下面看一下相关的数据:
00000140 01 00 01 00 01 00 00 00 52 02 00 00 04 00 00 00
|……..R…….|
00000150 70 10 03 00 00 00 0e 00
03 00 01 00 02 00 00 00
|p……………|
00000160 57 02 00 00 08 00 00 00
62 00 01 00 1a 01 02 00
|W…….b…….|
00000170 6e 20 02 00 10 00 0e 00
01 00 00 00 03 00 00 00 |n …………..|
- < init > 方法对应的 code_off 为 0x140,所以分析其数据应为:
名称 | 数据 | 含义 |
---|---|---|
registers_size | 0x01 | 此代码使用的寄存器数量为 1 |
ins_size | 0x01 | 此代码所用方法的传入参数的字数为 1 |
outs_size | 0x01 | 此代码进行方法调用所需的传出参数空间的字数为 1 |
tries_size | 0x00 | 此实例的 try_item 数量为 0 |
debug_info_off | 0x0252 | 从文件开头到此代码的调试信息(行号 + 局部变量信息)序列的偏移量为 0x0252 |
insns_size | 0x04 | 指令列表的大小(以 16 位代码单元为单位) |
insns | 0x1070 0x0003 0x0000 0x000e | invoke-direct {v0}, Ljava/lang/Object;.< init >:()V // method@0003 return-void |
padding | 无 | 无 |
tries | 无 | 无 |
handlers | 无 | 无 |
- public static main() 方法对应的 code_off 为 0x158,所以分析其数据应为:
名称 | 数据 | 含义 |
---|---|---|
registers_size | 0x03 | 此代码使用的寄存器数量为 3 |
ins_size | 0x01 | 此代码所用方法的传入参数的字数为 1 |
outs_size | 0x02 | 此代码进行方法调用所需的传出参数空间的字数为 2 |
tries_size | 0x00 | 此实例的 try_item 数量为 0 |
debug_info_off | 0x0257 | 从文件开头到此代码的调试信息(行号 + 局部变量信息)序列的偏移量为 0x0257 |
insns_size | 0x08 | 指令列表的大小(以 16 位代码单元为单位) |
insns | 0x0062 0x0001 0x011a 0x0002 0x206e 0x0002 0x0010 0x000e | sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 const-string v1, “Hello! My Dex!\n” // string@0002 invoke-virtual {v0, v1},Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 return-void |
padding | 无 | 无 |
tries | 无 | 无 |
handlers | 无 | 无 |
如何读取翻译 dalvik 字节码,请参考我的另一篇博文 Dalvik 字节码的读取
我们通过命令:
- dexdump -d Hello.dex
来解析一下 Hello.dex 文件,如下图所示,可以发现我们分析的与解析结果一般无二
1.3.3 map_list
引用自 header_item,即 header_item 中的 map_off 为 0x0274,其以 4 个字节对齐。
这是文件全部内容(依序显示)的列表。该列表包含关于 header_item 的一些冗余信息,但这些冗余信息存在的目的是提供一种用于遍历整个文件的简单方式。指定类型在映射中最多只能出现一次,但除了格式其余部分所隐含的限制条件(例如,header 区段必须先显示,随后是 string_ids 区段等等)之外,对于类型可以什么顺序显示,则没有任何限制。此外,映射条目必须按初始偏移量进行排序,不得重叠。(每个 map_item 占用 12 字节)
名称 | 格式 | 说明 |
---|---|---|
size | unit | 列表的大小(以条目数表示) |
list | map_item[size] | 列表的元素 |
由下面可知,size 为 0x0e 即 14 个,所以 map_list 总共占用空间大小为 4 + 14 × 12 = 172 字节,即 0xac,0x274 + 0xac = 0x320。所以看出从 map_off 处一直到文件末尾的数据都属于 map_list
00000270 02 00 00 00 0e 00 00 00
00 00 00 00 01 00 00 00 |…………….|
00000280 00 00 00 00 01 00 00 00 10 00 00 00 70 00 00 00 |…………p…|
00000290 02 00 00 00 07 00 00 00 b0 00 00 00 03 00 00 00 |…………….|
000002a0 03 00 00 00 cc 00 00 00 04 00 00 00 02 00 00 00 |…………….|
000002b0 f0 00 00 00 05 00 00 00 04 00 00 00 00 01 00 00 |…………….|
000002c0 06 00 00 00 01 00 00 00 20 01 00 00 01 20 00 00 |…….. …. ..|
000002d0 02 00 00 00 40 01 00 00 01 10 00 00 02 00 00 00 |….@………..|
000002e0 78 01 00 00 02 20 00 00 10 00 00 00 86 01 00 00 |x…. ……….|
000002f0 03 20 00 00 02 00 00 00 52 02 00 00 05 20 00 00 |. ……R…. ..|
00000300 01 00 00 00 5e 02 00 00 00 20 00 00 01 00 00 00 |….^…. ……|
00000310 61 02 00 00 00 10 00 00 01 00 00 00 74 02 00 00 |a………..t…|
00000320
总结
Dex 文件的结构图如下所示:
更多推荐
所有评论(0)