构造Dex文件
在讲解dex文件之前,需要先创建一个简单的例子来帮助来解析。不借助任何IDE工具就可以构造一个dex文件。用javac、dx命令即可。创建Java源文件,内容如下代码:
public class Hello{
public static void main(String[] args){
System.out.println("Hello, Android!\n");
}
}
然后通过javac命令编译成字节码,然后调用java命令机执行字节码。
javac Hello.java
java Hello
将.class文件转换成.dex文件
dx --dex --output=hello.dex Hello.class
将dex文件推送到sd卡上。
adb push Hello.dex /sdcard/
执行字节码
adb shell
dalvikvm -cp /sdcard/Hello.dex Hello
Dex文件格式
其各个成员解释如下:
-
header:是Dex文件头,类型为header_item。 -
string_ids:数组,元素类型为string_id_item,它存储和字符串相关的信息。 -
type_ids:数组,元素类型为type_id_item。存储类型相关的信息(由TypeDescriptor描述)。 -
field_ids:数组,元素类型为field_id_item,存储成员变量信息,包括变量名、类型等。 -
class_defs:数组,元素类型为class_def_item,存储类的信息。 -
data:Dex文件重要的数据内容都存在data区域里。一些数据结构会通过如xx_off这样的成员变量指向文件的某个位置,从该位置开始,存储了对应数据结构的内容,而xx_off的位置一般落在data区域里。 -
link_data:理论上是预留区域,没有特别的作用。
和Class文件格式比起来,Dex文件格式的特点如下:
- 有一个文件头,这个文件头对正确解析整个Dex文件至关重要。
- 有几个xxx_ids数组,包括string_ids(字符串相关)、type_ids(数据类型相关)、proto_ids(主要功能就是用于描述成员函数的参数、返回值类型,同时包含ShortyDescriptor信息)、field_ids(成员域相关)和method_ids(成员函数相关)。·data区域存储了绝大部分的内容,而data区域的解析又依赖于header和相关的数据项。
- data区域存储了绝大部分的内容,而data区域的解析又依赖于header和相关的数据项。
header_item
header_item是Dex文件头结构的类型(其实文件只是二进制数据的集合,这里只不过借用了编程语言的格式来描述它)。下图为header_item的数据结构伪代码。
head_item各个字段的解释如下:
- magic value:这8个字节一般是常量,为了使dex文件能够被识别出来,它必须出现在dex文件的最开头的位置。数组的值可以转换为一个字符串如下:{0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 } = "dex\n035\0",中间是一个 ’\n’ 符号后面035是dex文件格式的版本。
- checksum:checksum是文件校验码,使用alder32算法校验文件除去maigc、checksum外余下的所有文件区域,用于检查文件错误。
- signature:signature使用SHA-1算法hash除去magic、checksum和signature外余下的所有文件区域,用于唯一识别本文件。
- file_size:dex文件的大小。
- header_size:header区域的大小,单位字节,一般固定为0x70常量。
- endian_tag,表示文件内容应该按什么字节序来处理。默认取值为0x12345678,Little Endian格式。如果为Big Endian时,该字段取值为0x78563412。
- string_ids_size和string_ids_off。这两个字段表示dex中用到的所有字符串内容的大小和偏移值。
string_id_item等
xxx_id_item包括string_id_item、type_id_item、proto_id_item、field_id_item和method_id_item这五种数据结构,它们对应为string_ids、type_ids、proto_ids、field_ids和method_ids数组元素的数据类型,下图所示为它们的数据结构表示。
上中展示了dex文件中xxx_id_item的数据结构,下面是它们各字段的含义。
- string_data_item:utf16_size是字符串中字符的个数。Dex文件的字符串采用了变种的UTF8格式,对于英文字符和数字字符而言,都只占一个字节。data是字符串对应的内容。
- string_id_item:类似Class文件的CONSTANT_String类型,它只有一个成员
- string_data_off:用于指明string_data_item位于文件的位置,也就是索引。
- type_id_item:descriptor_idx是指向string_ids的索引。
- field_id_item:class_idx和type_idx是指向type_ids的索引,而name_idx是指向string_ids的索引。
- method_id_item:class_idx是指向type_ids的索引,proto_idx是指向proto_ids的索引,name_idx是指向string_ids的索引。
- proto_id_item:shorty_idx是指向string_ids的索引,return_type_idx是指向type_ids的索引,如果parameters_off不为0,则文件对应的地方存储类型为type_list的结构,用于描述函数参数的类型。
- type_item:type_idx是指向type_ids的索引。
- type_list:size表示list数组的个数,而list数组元素类型为type_item。函数的每一个参数都对应一个type_item元素。
此外还有几点内容请读者注意:
- string_ids、type_ids、proto_ids等都是中Dex文件结构的一部分,在代码中可通过数组来描述它们。
- 对于proto_id_item,首先,它的成员域shorty_idx(也就是ShortyDescriptor字符串)已经描述了参数和返回值的类型,但这只是简单描述,比如所有引用类型都用"L"统一表示,所以ShortyDescriptor肯定无法完整描述那种参数或者返回值类型为引用类型的函数。为解决此问题,proto_id_item中的return_type_idx用来描述返回值的数据类型,而参数的类型则通过parameters_off域(如果取值不为0,则表示该函数有参数)指向一个type_list。这个type_list为每个参数都存储了对应的数据类型(通过type_item中的type_idx来索引type_ids中的元素)。
class_def
本节会介绍代表类信息的class_def。下图为它的数据结构。
下图中class_def的几个主要成员变量的解释如下。
- class_idx:指向type_ids,代表本类的类型。
- access_flags:访问标志,比如private、public等。
- superclass_idx:指向type_ids,代表基类类型,如果没有基类则取值为NO_INDEX(值为-1)。
- interfaces_off:如果本类实现了某些接口,则interfaces_off指向文件对应位置,那里存储了一个type_list。该type_list的list数组存储了每一个接口类的type_idx索引(参考图3-5和对应的解释)。
- source_file_idx:指向string_ids,该类对应的源文件名。
- annotations_off:存储和注解有关的信息。
- class_data_off:指向文件对应位置,那里将存储更细节的信息,由class_data_item类型来描述。
- static_values_off:存储用来初始化类的静态变量的值,静态变量如果没有显示设置初值的话,默认是0或者null。如果有初值的话,初值信息就存储在文件static_values_off的地方,对应的数据结构名为encoded_array_item。
而一个类的成员变量、成员函数等信息则是通过图中class_data_off域指向一个名为class_data_item结构体来描述的。上图为与class_data_item相关的数据结构。
上图中最上面为class_data_item的数据结构。其中,static_fields_size、instance_fields_size、direct_methods_size和virtual_methods_size这四个成员变量分别决定了下述数组的长度。
- static_fields:类的静态成员信息,元素类型为encoded_field。
- instance_fields:类的非静态成员信息,元素类型为encoded_field。
- direct_methods:非虚函数信息,元素类型为encoded_method。
- virtual_methods:虚函数信息,元素类型为encoded_method。
而encoded_field和encoded_method用于描述类的成员变量和成员函数的信息。
-
encoded_field:field_idx_diff指向field_ids。注意这里是field_idx_diff,它表示除数组里第一个元素的field_idx_diff取值为索引值,该数组后续元素field_idx_diff取值为和前一个索引值的差。access_flags表示成员域的访问标志。
-
encoded_method:method_idx_diff指向method_ids。diff的含义与field_idx_diff一样。access_flags表示该函数的访问标志,code_off指向文件对应位置处,那里有一个类型为code_item的结构体,code_item类似于Class文件的Code属性。