Dex文件由dx(sdk里面)工具将class文件,整体打包而生成。
$ javac HelloWorld.java $ dx --dex --output=HelloWorld.dex HelloWorld.class
9大部分组成。
组成 |
---|
Header |
Strings |
Types |
Prototypes |
Fields |
Methods |
Classes |
Data |
LinkData |
说明:u1表示1个字节,u2表示两个字节,依次类推。
u1-u8的定义:
// mac 64位上的标准c定义 typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; // Android源代码中的定义 typedef uint8_t u1; typedef uint16_t u2; typedef uint32_t u4; typedef uint64_t u8;
struct DexHeader { u1 magic[8]; // 魔数固定 dex 035。035是版本号。 16进制:64 65 78 0A 30 33 35 00 u4 checksum; // 校验和 u1 signature[20]; // 签名 u4 fileSize; // 文件大小 u4 headerSize; // header的大小 112 u4 endianTag; // 字节序标记,用于指定dex运行环境的cpu,预设值为0x12345678, 即小字节序:78 56 34 12 u4 linkSize; // 链接段大小 一直为0 u4 linkOff; // 链接段的偏移 为0x0 u4 mapOff; // DexMapList文件的偏移 u4 stringIdsSize; // 字符串个数 u4 stringIdsOff; // 字符串的偏移 对应struct DexStringId u4 typeIdsSize; // 类型数量 u4 typeIdsOff; // 类型偏移 对应struct DexTypeId u4 protoIdsSize; // 方法原型的个数 u4 protoIdsOff; // 偏移 对应struct DexProtoId u4 fieldIdsSize; // 字段个数 u4 fieldIdsOff; // 字段偏移 对应struct DexFieldId u4 methodIdsSize; // 方法个数 u4 methodIdsOff; // 方法偏移 对应struct DexMethodId u4 classDefsSize; // 类的个数 u4 classDefsOff; // 类的偏移 对应struct DexClassDef u4 dataSize; // 数据段的大小 u4 dataOff; // 数据段偏移 };
stringIdsSize:记录了整个dex文件中有多少个字符串,dex里面的文件不会重复。
stringIdsOff:记录字符串在dex文件中的偏移位置。即第一个字符串的位置。
比如上面的图片中:
stringIdsSize=1C 00 00 00, 16进制的1C=28(10进制),表示此dex文件中有28个字符串。
stringIdsOff=70 00 00 00,表示:字符串偏移到0x70的位置(即从文件开始数112个字节位),也就是070h:B2 02 00 00。
stringIdsOff存放的不是真正的字符串,实际的字符串存放在data段里面,这里表示的是实际的字符串偏移位置。
stringIdsOff的结构如下:
struct DexStringId { u4 stringDataOff; // 真实的字符串位置 };
寻找真实字符串的步骤:先找到字符串的偏移位置stringIdsOff,然后通过stringIdsOff找到真实的字符串。
比如:上面的dex文件有28个字符串,我们找第一个字符串。
第一个字符串的偏移位置是 stringIdsOff=70 00 00 00
,也就是70h的位置 B2 02 00 00
,然后再找到 B2 02 00 00
在文件中的位置(注意小字节序实际是02B2H的位置) 02B2H的位置是: 08
,表示的是后面8个字节组合起来就是真正的字符串了,即: 3C 63 6C 69 6E 69 74 3E 00
, 注意后面有个00,表示字符串的结尾(/0)。根据16进制查找ascii码表 可以得到: <clinit>
。
第二个字符串,紧接其后, 06
表示后面6个字节表示第二个字符串, 3C 69 6E 69 74 3E 00
查表可知是 <init>
。或者也可以共同过stringIdsOff来找。
字符串的偏移地址是: 0x70
,第二个就是0x70 + stringDataOff的长度4,即0x74得到第二个字符串的data偏移地址: BC 02 00 00
即 0x02BC
,然后在到 0x2bc
找到真实的字符串即: 06 3C 63 6C 69 6E 69 74 3E 00
。
索引号 | 长度 | 字符串 |
---|---|---|
0 | 8 | <clinit> |
1 | 6 | <init> |
2 | 11 | HelloDalvik |
3 | 9 | HelloDex! |
4 | 15 | HelloWorld.java |
5 | 1 | I |
6 | 3 | III |
7 | 1 | L |
8 | 12 | LHelloWorld; |
9 | 2 | LI |
10 | 2 | LL |
11 | 21 | Ljava/io/PrintStream; |
12 | 18 | Ljava/lang/Object; |
13 | 18 | Ljava/lang/String; |
14 | 25 | Ljava/lang/StringBuilder; |
15 | 18 | Ljava/lang/System; |
16 | 1 | V |
17 | 2 | VL |
18 | 19 | [Ljava/lang/String; |
19 | 1 | a |
20 | 6 | append |
21 | 1 | b |
22 | 9 | getNumber |
23 | 4 | main |
24 | 3 | out |
25 | 7 | println |
26 | 8 | toString |
27 | 7 | valueOf |
typeIdsSize:记录了dex文件中的类型
typeIdsOff:偏移地址,指向的是 struct DexTypeId
结构
struct DexTypeId { u4 descriptorIdx; // 表示的是上面28个字符串的索引(序号) };
descriptorIdx:表示的是上面28个字符串的索引(序号), 假如descriptorIdx的值是1,那么根据上面的分析,第一个(索引0)字符串是 <clinit>
,第二个(索引1)是 <init>
,那么就是指代 <init>
。
解析举例:
根据上面的图片找到 typeIdsSize=09 00 00 00
,即有9个typeId, typeIdsOff=E0 0 00 00
偏移地址是: 0x00E0
,然后找到 0x00E0
是 05 00 00 00
, 16进制转换为10进制就是5,也就是说,第一个type指向的是字符串第5个,查找上面的表格可知类型是 I
,即java中的int。
序号 | 字符串索引 | 结果 | java中的含义 |
---|---|---|---|
0 | 5 | I | int |
1 | 8 | LHelloWorld; | class HelloWorld |
2 | 11 | Ljava/io/PrintStream; | class PrintStream |
3 | 12 | Ljava/lang/Object; | class Object |
4 | 13 | Ljava/lang/String; | class String |
5 | 14 | Ljava/lang/StringBuilder; | class StringBuilder |
6 | 15 | Ljava/lang/System; | class System |
7 | 16 | V | void |
8 | 18 | [Ljava/lang/String; | class String 数组,new String[] |
protoIdsSize:方法原型的个数
protoIdsOff:偏移, 结构是 struct DexProtoId
struct DexProtoId { u4 shortyIdx; // 指向strings的索引号, 同type类似。 u4 returnTypeIdx; // 指向type的索引号 表示的是返回类型 u4 parametersOff; // 参数列表的偏移 表示的是方法的参数列表 };
parametersOff:指向的数据结构
struct DexTypeList { u4 size; // 表示下面DexTypeItem的个数 DexTypeItem list[1]; // DexTypeItem的数据格式 }; struct DexTypeItem { u2 typeIdx; // 指向types的索引号 };
解析步骤举例:
protoIdsSize=07 00 00 00
, 表示有7个方法原型 protoIdsOff=04 01 00 00
,表示第一个的偏移位置是 0x104
0x104
的位置 06 00 00 00 00 00 00 00 94 02 00 00
,因为DexProtoId是有3个u4组成所以是12个字节 shortyIdx=06 00 00 00
,即表示是28个字符串中的第6个, III
returnTypeIdx=00 00 00 00
,即9个type中的第1个(索引0), I
parametersOff= 94 02 00 00
,表示参数列表偏移地址是 0x294
,即第一个 struct DexTypeList
的位置。 0x294
是 02 00 00 00
即10进制的2,表示后面有2个 struct DexTypeItem
每个占2个字节。 所以整个 struct DexTypeList
的值就是 02 00 00 00 00 00 00 00
两个分别是 00 00
和 00 00
表示9个types的第1个,分别是 I
和 I
,所以整合起来就是返回I(II),即int (int int)。 序号 | 字符串的索引号 | 值 |
---|---|---|
0 | 6 | III |
1 | 7 | L |
2 | 9 | LI |
3 | 10 | LL |
4 | 16 | V |
5 | 17 | VL |
6 | 17 | VL |
序号 | types的索引号 | 值 |
---|---|---|
0 | 0 | I |
1 | 4 | Ljava/lang/String; |
2 | 4 | Ljava/lang/String; |
3 | 5 | Ljava/lang/StringBuilder; |
4 | 7 | V |
5 | 7 | V |
6 | 7 | V |
序号 | size | DexTypeItem(type索引号) | 值 |
---|---|---|---|
0 | 2 | 0 和0 | I 和 I |
1 | 0 | 无 | () |
2 | 1 | 0 | I |
3 | 1 | 4 | Ljava/lang/String; |
4 | 0 | 无 | () |
5 | 1 | 4 | Ljava/lang/String; |
6 | 1 | 8 | [Ljava/lang/String; |
综上可以得到方法原型(删去函数名字和变量名):
序号 | 值 |
---|---|
0 | int (int int) |
1 | String () |
2 | String (int) |
3 | StringBuilder (StringBuilder) |
4 | void () |
5 | void (String) |
6 | void (String[]) |
fieldIdsSize: dex文件中字段的个数,4个字节
fieldIdsOff:字段的偏移地址。指向的是 struct DexFieldId
的结构,每一个结构占8个字节
struct DexFieldId { u2 classIdx; // 9个type的索引 u2 typeIdx; // 9个type的索引 u4 nameIdx; // 28个字符串的索引 };
分析举例:
fieldIdsSize=03 00 00 00
,表示有3个字段 fieldIdsOff=58 01 00 00
,表示字段的偏移地址是 0x158
0x158
的位置 01 00 00 00 13 00 00 00
,即DexFieldId的值。 所以第一个字段: struct DexFieldId { u2 classIdx = 01 00 ; // 9个type的索引的第2个(索引1) u2 typeIdx = 00 00; // 9个type的索引的第1个(索引0) u4 nameIdx = 13 00 00 00; // 28个字符串的索引的第20个(索引19), 13 00 00 00 表示的是16进制 };查上面的各个表可知,第一个字段是:
struct DexFieldId { u2 classIdx = LHelloWorld ; // 9个type的索引的第2个(索引1) u2 typeIdx = I; // 9个type的索引的第1个(索引0) u4 nameIdx = a; // 28个字符串的索引的第20个(索引19) };整合起来的意思是:class HelloWorld中有一个int a字段。目前还不知道访问限制。
class HelloWorld { int a; }
序号 | 结果 |
---|---|
0 | int HelloWorld.a |
1 | String HelloWorld.b |
3 | PrintStream java.lang.System.out |
methodIdsSize: 方法个数 4个字节 methodIdsOff: 方法偏移 4个字节, 对应 struct DexMethodId
每个占8个字节
struct DexMethodId { u2 classIdx; // 9个type的索引 u2 protoIdx; // 7个proto索引 u4 nameIdx; //28个字符串的索引 };
解析实例:
methodIdsSize=0A 00 00 00
即10个 methodIdsOff=70 01 00 00
即偏移位置是 0x170
0x170
的位置,第一个DexMethodId是 01 00 04 00 00 00 00 00
。按长度分配。 ```c struct DexMethodId { u2 classIdx = 01 00; // 9个type的索引,即第2个(索引1) u2 protoIdx = 04 00; // 7个proto索引,即第5个(索引4) u4 nameIdx = 00 00 00 00; //28个字符串的索引,即第1个(索引0) }; 查上面的各个表可知: ```c struct DexMethodId { u2 classIdx = LHelloWorld; // 9个type的索引,即第2个(索引1) u2 protoIdx = void(); // 7个proto索引,即第5个(索引4) u4 nameIdx = `<clinit>; //28个字符串的索引,即第1个(索引0) };
还原成java即:
class HelloWorld { // 这是编译器自动添加的,就是静态构造函数。 void `<clinit>`() { } }
序号 | 方法 |
---|---|
0 | void <clinit> () |
1 | void <linit> () |
2 | int getNumber(int, int) |
3 | void main (String[]) |
4 | void PrintStream.println(String) |
5 | void Object. <init> () |
6 | String String.valueOf (int) |
7 | void StringBuilder. <init> () |
8 | StringBuilder StringBuild.append (String) |
9 | String StringBuild.toString () |
classDefsSize: 类的个数 4个字节 classDefsOff: 类的偏移位置,4个字节, 对应 struct DexClassDef
32个字节
struct DexClassDef { u4 classIdx; // 9个type的索引 u4 accessFlags; // 访问限制, public private default protected u4 superclassIdx; // 9个type的索引, 父类 u4 interfacesOff; // DexTypeList的偏移地址, 对应interface u4 sourceFileIdx; // 28个string u4 annotationsOff; // 注解的偏移地址 DexAnnotationsDirectoryItem u4 classDataOff; /* file offset to class_data_item */ u4 staticValuesOff; // 静态字段 DexEncodedArray };
类的结构比较复制。 子结构后面涉及到了子列出。
分析步骤:
classDefsSize=01 00 00 00
即只有一个类 classDefsOff=C0 01 00 00
即是第一个类的偏移地址 0x1C0
0x1C0
的位置,struct DexClassDef是32个字节所以 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 F8 03 00 00 00 00 00 00
。 struct DexClassDef { u4 classIdx = 01 00 00 00; // 9个type的索引 u4 accessFlags = 01 00 00 00; // 访问限制, public private default protected u4 superclassIdx = 03 00 00 00; // 9个type的索引, 父类 u4 interfacesOff = 00 00 00 00; // DexTypeList的偏移地址, 对应interface 偏移地址为0,表示没有 u4 sourceFileIdx = 04 00 00 00; // 28个string u4 annotationsOff = 00 00 00 00; // 注解的偏移地址 DexAnnotationsDirectoryItem 偏移地址为0,表示没有 u4 classDataOff = F8 03 00 00; /* file offset to class_data_item */ u4 staticValuesOff = 00 00 00 00; // 静态字段 DexEncodedArray, 偏移地址为0,表示没有 };
查上面的表可知:
struct DexClassDef { u4 classIdx = LHelloWorld; // 9个type的索引 u4 accessFlags = public; // 访问限制, public private default protected u4 superclassIdx = Ljava/lang/Object; // 9个type的索引, 父类 u4 interfacesOff = 无; // DexTypeList的偏移地址, 对应interface 偏移地址为0,表示没有 u4 sourceFileIdx = HelloWorld.java; // 28个string u4 annotationsOff = 无; // 注解的偏移地址 DexAnnotationsDirectoryItem 偏移地址为0,表示没有 u4 classDataOff = F8 03 00 00; // DexClassData结构 u4 staticValuesOff = 无; // 静态字段 DexEncodedArray, 偏移地址为0,表示没有 };
访问限制枚举:
// dalvik/libdex/DexFile.h ACC_PUBLIC = 0x00000001, // class, field, method, ic ACC_PRIVATE = 0x00000002, // field, method, ic ACC_PROTECTED = 0x00000004, // field, method, ic ACC_STATIC = 0x00000008, // field, method, ic ACC_FINAL = 0x00000010, // class, field, method, ic ACC_SYNCHRONIZED = 0x00000020, // method (only allowed on natives) ACC_SUPER = 0x00000020, // class (not used in Dalvik) ACC_VOLATILE = 0x00000040, // field ACC_BRIDGE = 0x00000040, // method (1.5) ACC_TRANSIENT = 0x00000080, // field ACC_VARARGS = 0x00000080, // method (1.5) ACC_NATIVE = 0x00000100, // method ACC_INTERFACE = 0x00000200, // class, ic ACC_ABSTRACT = 0x00000400, // class, method, ic ACC_STRICT = 0x00000800, // method ACC_SYNTHETIC = 0x00001000, // field, method, ic ACC_ANNOTATION = 0x00002000, // class, ic (1.5) ACC_ENUM = 0x00004000, // class, field, ic (1.5) ACC_CONSTRUCTOR = 0x00010000, // method (Dalvik only) ACC_DECLARED_SYNCHRONIZED = 0x00020000, // method (Dalvik only) ACC_CLASS_MASK = (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM), ACC_INNER_CLASS_MASK = (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC), ACC_FIELD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM), ACC_METHOD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR | ACC_DECLARED_SYNCHRONIZED),
由于接口 注解 静态字段没有这里我们值分析classDataOff。
classDataOff=F8 03 00 00,即表示这个classData的偏移地址是0x03F8。
struct DexClassData { DexClassDataHeader header; // 头 DexField* staticFields; // 静态字段 DexField* instanceFields; // 实例字段 DexMethod* directMethods; // 直接方法 DexMethod* virtualMethods; // 虚方法 }; struct DexClassDataHeader { u4 staticFieldsSize; // 静态字段数 u4 instanceFieldsSize; // 实例字段数 u4 directMethodsSize; // 直接方法 u4 virtualMethodsSize; // 虚方法 };
Leb128转换规则:
最高位1:表示有后续字节 为0:表示没有后续字节 转换时去掉最高位,从低到高从新组合。 比如:0000 0001 去掉最高位: 000 0001 结果还是1
根据数据转换格式后可知:
struct DexClassDataHeader { u4 staticFieldsSize = 0x1; // 静态字段数 u4 instanceFieldsSize = 0x1; // 实例字段数 u4 directMethodsSize = 0x3; // 直接方法 u4 virtualMethodsSize = 0x1; // 虚方法 };
struct DexField { u4 fieldIdx; // 指向字段索引号 u4 accessFlags; // 访问表示 };
DexClassDataHeader实际占用了4个字节,所以staticFields紧接其后 01
即1,和 08
即8。
struct DexField { u4 fieldIdx = String HelloWorld.b; // 指向字段索引号, u4 accessFlags = ACC_STATIC; // 访问表示 };
然后是instanceFields:00 和 00。
struct DexField { u4 fieldIdx = int HelloWorld.a; // 指向字段索引号0 u4 accessFlags = 无; // };
接着是DexMethod directMethods。
struct DexMethod { u4 methodIdx; // 方法索引 u4 accessFlags; u4 codeOff; // 代码放置的偏移位置 DexCode结构 };
void <clinit> ()
后面的数据需要使用leb128转换,因为0x88的二进制是 1000 1000
最高位为1,说明后面还有字节,继续看0x80, 1000 0000
后面还有,在看0x04没有了, 表明accessFlags是3个字节组成,即0x88 80 04,我们转换一下 10001000 10000000 00000100
,去掉每一个字节的高位 0001000 0000000 0000100
, 然后按小字节序排列。得到 0000100 000000 0001000
,即16进制的0x10008,也就是ACC_STATIC和ACC_CONSTRUCTOR。
codeOff:E0 03。也需要转换,即 0x1E0
,表示code放的位置。
struct DexCode { u2 registersSize; // 需要寄存器的个数 u2 insSize; // 需要的参数个数 u2 outsSize; // 调用其他函数的参数个数 u2 triesSize; // DexTry 的个数 try catch u4 debugInfoOff; // 偏移地址,指向本段代码的 debug 信息存放位置,是一个 debug_info_item 结构 u4 insnsSize; // 指令列表的大小,以 16-bit 为单位。 insns 是 instructions 的缩写 u2 insns[1]; // 指令集 };
0x1E0: 01 00
00 00
00 00
00 00
D8 03 00 00
05 00 00 00
1A...0E 00
struct DexCode { u2 registersSize = 1; // 需要寄存器的个数 u2 insSize = 0; // 需要的参数个数 u2 outsSize = 0; // 调用其他函数的参数个数 u2 triesSize = 0; // DexTry 的个数 try catch u4 debugInfoOff = 0x03D8; // 偏移地址,指向本段代码的 debug 信息存放位置,是一个 debug_info_item 结构 u4 insnsSize = 5; // 指令列表的大小,以 16-bit 为单位。 insns 是 instructions 的缩写, 最大为65535 u2 insns[1] = ; // 指令集,参考https://source.android.com/devices/tech/dalvik/dalvik-bytecode };
至此已经分析完了一个directMethod还有2个以及virtualMethod方法,自行尝试分析。
附:
public class HelloWorld { int a = 0; static String b = "HelloDalvik"; public int getNumber(int i, int j) { int e = 3; return e + i + j; } public static void main(String[] args) { int c = 1; int d = 2; HelloWorld helloWorld = new HelloWorld(); String sayNumber = String.valueOf(helloWorld.getNumber(c, d)); System.out.println("HelloDex!" + sayNumber); } }
参考: