.dex
文件是 Dalvik 字节码的传输格式。一个文件要成为有效的 .dex
文件,必须在语法和语义上遵循一定的约束条件;此外,还要求运行时仅支持有效的 .dex 文件。
.dex 的一般完整性约束
一般完整性约束涉及较大结构的 .dex
文件,详见 .dex
格式。
标识符 | 说明 |
---|---|
G1 | .dex 文件的 magic 数值必须是 dex\n035\0 或 dex\n037\0 。 |
G2 | 校验和必须是除了 magic 和 checksum 字段之外的整个文件内容的 Adler-32 校验和。 |
G3 | 签名必须是除了 magic 、checksum 和 signature 之外的整个文件内容的 SHA-1 哈希值。 |
G4 | file_size 必须与实际文件大小(以字节为单位)相匹配。 |
G5 | header_size 的值必须为 0x70 |
G6 | endian_tag 的值必须为 ENDIAN_CONSTANT 或 REVERSE_ENDIAN_CONSTANT |
G7 |
对于 link 、string_ids 、type_ids 、proto_ids 、field_ids 、method_ids 、class_defs 和 data 这些区段,offset 和 size 字段必须都为零或都非零。在后一种情况下,偏移必须四字节对齐。 |
G8 | 标头中除 map_off 之外的所有偏移字段都必须四字节对齐。 |
G9 |
map_off 字段必须为零或指向数据区段。在后一种情况下,必须存在 data 区段。 |
G10 |
link 、string_ids 、type_ids 、proto_ids 、field_ids 、method_ids 、class_defs 和 data 区段都不得彼此重叠或者与标头重叠。 |
G11 | 如果存在映射,则每个映射条目都必须具有有效的类型。每种类型最多可以出现一次。 |
G12 |
如果存在映射,每个映射条目的偏移和大小必须不能为零。偏移必须指向文件的相应区段(即,string_id_item 必须指向 string_ids 区段),并且项的显式或隐式大小必须与该区段的实际内容和大小相匹配。 |
G13 |
如果存在映射,映射条目 n+1 的偏移量必须大于或等于映射条目 n plus than size of map entry n 的偏移量。这样条目才能互不重叠且从低到高排序。 |
G14 |
以下类型的条目必须具有四字节对齐的偏移量:string_id_item 、type_id_item 、proto_id_item 、field_id_item 、method_id_item 、class_def_item 、type_list 、code_item 和 annotations_directory_item 。 |
G15 |
对于每个 string_id_item ,string_data_off 字段必须包含对 data 区段的有效引用。对于引用的 string_data_item ,data 字段必须包含有效的 MUTF-8 字符串,并且 utf16_size 必须与字符串的解码长度匹配。 |
G16 |
对于每个 type_id_item ,descriptor_idx 字段必须包含对 string_ids 列表的有效引用。引用的字符串必须是有效的类型描述符。 |
G17 |
对于每个 proto_id_item ,shorty_idx 字段必须包含对 string_ids 列表的有效引用。引用的字符串必须是有效的短描述符。此外,return_type_idx 字段必须是 type_ids 区段的有效索引,并且 parameters_off 字段必须为零或指向 data 区段的有效偏移量。如果为非零,参数列表不得包含任何空白条目。 |
G18 |
对于每个 field_id_item ,class_idx 和 type_idx 字段都必须是 type_ids 列表的有效索引。class_idx 引用的条目必须是非数组引用类型。此外,name_idx 字段必须是对 string_ids 区段的有效引用,且引用条目的内容必须符合 MemberName 规范。 |
G19 |
对于每个 method_id_item ,class_idx 字段必须是 type_ids 区段的有效索引,且引用的条目必须是非数组引用类型。proto_id 字段必须是对 proto_ids 列表的有效引用。name_idx 字段必须是对 string_ids 区段的有效引用,且引用条目的内容必须符合 MemberName 规范。 |
G20 |
对于每个 field_id_item ,class_idx 字段必须是 type_ids 列表的有效索引。引用的条目必须是非数组引用类型。 |
静态字节码约束
静态约束是对字节码的各个元素的约束。通常,可以在不使用控制或数据流分析技术的情况下检查这类约束。
标识符 | 说明 |
---|---|
A1 |
insns 数组不能为空。 |
A2 | insns 数组中第一个运算码的索引必须为零。 |
A3 | insns 数组必须只包含有效的 Dalvik 运算码。 |
A4 | 指令 n+1 的索引必须等于指令 n 的索引加上指令 n 的长度,同时要考虑可能的运算数。 |
A5 | insns 数组中最后一条指令必须在索引 insns_size-1 处结尾。 |
A6 | 所有 goto 和 if-<kind> 目标必须是同一方法中的运算码。 |
A7 |
packed-switch 指令的所有目标必须是同一方法中的运算码。目标的大小和列表必须一致。 |
A8 |
sparse-switch 指令的所有目标必须是同一方法中的运算码。相应的表必须一致,并从低到高排序。 |
A9 |
const-string 和 const-string/jumbo 指令的 B 运算数必须是字符串常量池的有效索引。 |
A10 |
iget<kind> 和 iput<kind> 指令的 C 运算数必须是字段常量池的有效索引。引用的条目必须表示一个实例字段。 |
A11 |
sget<kind> 和 sput<kind> 指令的 C 运算数必须是字段常量池的有效索引。引用的条目必须表示静态字段。 |
A12 |
invoke-virtual 、invoke-super 、invoke-direct 和 invoke-static 指令的 C 运算数必须是方法常量池的有效索引。 |
A13 |
invoke-virtual/range 、invoke-super/range 、invoke-direct/range 和 invoke-static/range 指令的 B 运算数必须是方法常量池的有效索引。 |
A14 |
名称以“<”开头的方法只能由虚拟机隐式调用,而不能由源自 .dex 文件的代码调用。唯一的例外是实例初始化程序,它可以由 invoke-direct 调用。 |
A15 |
invoke-interface 指令的 C 运算数必须是方法常量池的有效索引。引用的 method_id 必须属于一个接口(而不是类)。 |
A16 |
invoke-interface/range 指令的 B 运算数必须是方法常量池的有效索引。引用的 method_id 必须属于一个接口(而不是类)。 |
A17 |
const-class 、check-cast 、new-instance 和 filled-new-array/range 指令的 B 运算数必须是类型常量池的有效索引。 |
A18 |
instance-of 、new-array 和 filled-new-array 指令的 C 运算数必须是类型常量池的有效索引。 |
A19 |
由 new-array 指令创建的数组维数必须小于 256 。 |
A20 |
new 指令不得引用数组类、接口和抽象类。 |
A21 |
new-array 指令引用的类型必须是有效的非引用类型。 |
A22 |
指令以单精度宽度(非值对)形式引用的所有寄存器必须对当前方法有效。也就是说,它们的索引必须是非负数,并且小于 registers_size 。 |
A23 |
指令以双精度宽度(值对)形式引用的所有寄存器必须对当前方法有效。也就是说,它们的索引必须是非负数,并且小于 registers_size-1 。 |
A24 |
invoke-virtual 和 invoke-direct 指令的 method_id 运算数必须属于一个类(而不是一个接口)。在版本 037 之前的 Dex 文件中,invoke-super 和 invoke-static 指令也应如此。 |
A25 |
invoke-virtual/range 和 invoke-direct/range 指令的 method_id 运算数必须属于一个类(而不是一个接口)。在版本 037 之前的 Dex 文件中,invoke-super/range 和 invoke-static/range 指令也应如此。 |
结构字节码约束
结构约束是对字节码的若干元素之间的关系的约束。通常,在不使用控制或数据流分析技术的情况下无法检查这类约束。
标识符 | 说明 |
---|---|
B1 | 参数(寄存器和立即值)的数量和类型必须始终与指令相匹配。 |
B2 | 寄存器对绝不能分解。 |
B3 | 必须先分配寄存器(或寄存器对),然后才能读取。 |
B4 |
invoke-direct 指令只能调用当前类或其一个超类中的实例初始化程序或方法。 |
B5 | 只能在未初始化的实例上调用实例初始化程序。 |
B6 | 只能在已初始化的实例上调用实例方法,并且只能在已初始化的实例上访问实例字段。 |
B7 |
如果在实例初始化之前再次执行相同的 new-instance 指令,则不能使用保留 new-instance 指令结果的寄存器。 |
B8 |
一个实例初始化程序必须先调用另一个实例初始化程序(相同的类或超类),才能获取任何实例成员。例外情况是非继承的实例字段,通常可在调用另一个初始化程序和 Object 类之前进行分配。 |
B9 | 所有实际的方法参数必须与其各自的形式参数分配兼容。 |
B10 | 对于每个实例方法调用,实际实例必须与指令中指定的类或接口分配兼容。 |
B11 | return<kind> 指令必须与其方法的返回类型匹配。 |
B12 | 获取超类的受保护成员时,被获取实例的实际类型必须是当前类或它的其中一个子类。 |
B13 | 存储到静态字段中的值的类型必须与指定的字段类型兼容或可转换为字段的类型。 |
B14 | 存储在字段中的值的类型必须与指定的字段类型兼容或可转换为字段的类型。 |
B15 | 存储到数组中的每个值的类型必须与指定给数组的组件类型兼容。 |
B16 |
throw 指令的 A 运算数必须与 java.lang.Throwable 的分配项兼容。 |
B17 |
方法的最后一个可触及指令必须是向后的 goto 或分支、return 或 throw 指令。必须确保不会将 insns 数组留在底部。 |
B18 | 之前寄存器对中未分配的一半地址可能不会被读取(会被视为无效),直到另外一些指令重新分配它们之后才会被读取。 |
B19 |
move-result<kind> 指令前面(在 insns 数组中)必须紧接 invoke-<kind> 指令。唯一的例外情况是 move-result-object 指令,该指令前面也可以接 filled-new-array 指令。 |
B20 |
move-result<kind> 指令前面(在实际控制流中)必须紧接匹配的 return-<kind> 指令(不能跳转到该指令)。唯一的例外情况是 move-result-object 指令,该指令前面也可以接 filled-new-array 指令。 |
B21 |
move-exception 指令必须仅作为异常处理程序中的第一条指令出现。 |
B22 |
控制流不得触及 packed-switch-data 、sparse-switch-data 和 fill-array-data 伪指令。 |