.dex(Dalvik Executable Format)文件用于保存类定义及其关联的辅助数据
文件格式
header文件头
数据结构
// Raw header_item.
struct Header {
uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_
uint8_t signature_[kSha1DigestSize];
uint32_t file_size_; // size of entire file
uint32_t header_size_; // offset to start of next section
uint32_t endian_tag_;
uint32_t link_size_; // unused
uint32_t link_off_; // unused
uint32_t map_off_; // unused
uint32_t string_ids_size_; // number of StringIds
uint32_t string_ids_off_; // file offset of StringIds array
uint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535
uint32_t type_ids_off_; // file offset of TypeIds array
uint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535
uint32_t proto_ids_off_; // file offset of ProtoIds array
uint32_t field_ids_size_; // number of FieldIds
uint32_t field_ids_off_; // file offset of FieldIds array
uint32_t method_ids_size_; // number of MethodIds
uint32_t method_ids_off_; // file offset of MethodIds array
uint32_t class_defs_size_; // number of ClassDefs
uint32_t class_defs_off_; // file offset of ClassDef array
uint32_t data_size_; // unused
uint32_t data_off_; // unused
......
}
- file_size_: 整个文件的大小,单位字节
- header_size_: 文件头的大小,单位字节
- string_ids_size_: 字符串列表中字符串的数量
- string_ids_off_: 字符串列表在文件中的偏移
- type_ids_size_: 类型列表中元素数量
- type_ids_off_: 类型列表在文件中的偏移
- proto_ids_size_: 方法原型列表元素数量
- proto_ids_off_: 方法原型列表在文件中的偏移
- field_ids_size_: 字段列表的元素数量
- field_ids_off_: 字段列表在文件中的偏移
- method_ids_size_: 方法列表元素的数量
- method_ids_off_: 方法列表在文件中的偏移
- class_defs_size_: 类定义列表中元素数量
- class_defs_off_: 类定义列表在文件中的偏移
- data_size_: data段的大小
- data_off_: data段在文件中的偏移
例子
DEX file header:
magic : 'dex\n035\0'
checksum : 1181417f
signature : a022...4f7a
file_size : 2727984
header_size : 112
link_size : 0
link_off : 0 (0x000000)
string_ids_size : 19080
string_ids_off : 112 (0x000070)
type_ids_size : 2332
type_ids_off : 76432 (0x012a90)
proto_ids_size : 3091
proto_ids_off : 85760 (0x014f00)
field_ids_size : 8477
field_ids_off : 122852 (0x01dfe4)
method_ids_size : 17750
method_ids_off : 190668 (0x02e8cc)
class_defs_size : 1681
class_defs_off : 332668 (0x05137c)
data_size : 2325048
data_off : 402936 (0x0625f8) ===> 类定义列表与data段有间隙
string_ids字符串列表
文件使用的所有字符串的标识符,用于内部命名(例如类型描述符)或用作代码引用的常量对象。此列表必须使用 UTF-16 代码点值按字符串内容进行排序,且不得包含任何重复条目。
元素数据结构
// Raw string_id_item.
struct StringId {
uint32_t string_data_off_; // offset in bytes from the base address
private:
DISALLOW_COPY_AND_ASSIGN(StringId);
};
-
string_data_off_: 字符串数据在文件中的偏移量,该偏移量应该在data段中,采用
string_data_item
指定的格式名称 格式 说明 utf16_size uleb128 字符串的长度 data ubyte[] 一系列MUTF-8代码单元 对于leb128以及MUTF-8请参考https://source.android.com/devices/tech/dalvik/dex-format?hl=zh-cn
例子
......
00000070 08 5d 18 00 0a 5d 18 00 0d 5d 18 00 22 5d 18 00 |.]...]...].."]..|
00000080 38 5d 18 00 3b 5d 18 00 3f 5d 18 00 44 5d 18 00 |8]..;]..?]..D]..|
......
string_ids => 0x70
第一个StringId(string_id_item)的string_data_off_为0x00185d08
第二个StringId(string_id_item)的string_data_off_为0x00185d0a
第三个StringId(string_id_item)的string_data_off_为0x00185d0d
第四个StringId(string_id_item)的string_data_off_为0x00185d22
......
data段的字符串信息:
00185d00 00 00 00 00 00 00 00 00 00 00 01 0a 00 13 0a 20 |............... |
00185d10 20 43 6c 69 65 6e 74 20 76 65 72 73 69 6f 6e 3a | Client version:|
00185d20 20 00 14 0a 20 20 53 65 72 76 69 63 65 20 76 65 | ... Service ve|
第一个字符串二进制: 00 00
第二个字符串二进制: 01 0a 00
第三个字符串二进制: 13 0a 20 20 43 6c 69 65 6e 74 20 76 65 72 73 69 6f 6e 3a 20 00
type_ids类型列表
类型标识符列表,包含文件引用的所有类型(类、数组或原始类型)的标识符,此列表必须按 string_id 索引进行排序,且不得包含任何重复条目。
元素数据结构
// Raw type_id_item.
struct TypeId {
uint32_t descriptor_idx_; // index into string_ids
private:
DISALLOW_COPY_AND_ASSIGN(TypeId);
};
- descriptor_idx_: 类描述符在string_ids列表中的索引
例子
type_ids位置:
00012a90 a5 04 00 00 9d 05 00 00 0b 07 00 00 6c 08 00 00 |............l...|
00012aa0 72 09 00 00 21 0a 00 00 06 0b 00 00 07 0b 00 00 |r...!...........|
第一个类描述符在string_ids列表中的索引: 0x000004a5 (位置: 0x70 + 0x4a5 * 4 = 0x1304)
对应的string_ids列表内容:
00001300 65 63 19 00 7a 63 19 00 7d 63 19 00 81 63 19 00 |ec..zc..}c...c..|
(0x0019637a)对应的data段的字符串信息:
00196370 65 44 65 6c 65 67 61 74 65 00 01 42 00 02 42 3a |eDelegate..B..B:|
对应的类型为B(基本类型byte)
proto_ids方法原型列表
方法原型标识符列表,包含文件引用的所有原型的标识符
元素数据结构
// Raw proto_id_item.
struct ProtoId {
uint32_t shorty_idx_; // index into string_ids array for shorty descriptor
uint16_t return_type_idx_; // index into type_ids array for return type
uint16_t pad_; // padding = 0
uint32_t parameters_off_; // file offset to type_list for parameter types
private:
DISALLOW_COPY_AND_ASSIGN(ProtoId);
};
shorty_idx_: 方法原型的简短描述符字符串在string_ids列表中的索引
return_type_idx_: 方法原型的返回类型在type_ids列表中的索引
-
parameters_off_: 方法原型的参数类型列表在文件中的偏移量,如果没有参数,该值为0;该偏移量应该位于data段中,数据采用type_list指定的格式
名称 格式 说明 size uint 列表的大小 list type_item[size] 列表元素 - type_item格式
名称 格式 说明 type_idx ushort 在type_ids列表中的索引
field_ids字段列表
元素数据结构
// Raw field_id_item.
struct FieldId {
uint16_t class_idx_; // index into type_ids_ array for defining class
uint16_t type_idx_; // index into type_ids_ array for field type
uint32_t name_idx_; // index into string_ids_ array for field name
private:
DISALLOW_COPY_AND_ASSIGN(FieldId);
};
- class_idx_: 定义该字段的类在type_ids中的索引
- type_idx_: 字段的类型在type_ids中的索引
- name_idx_: 字段的名称在string_ids中的索引
method_ids方法列表
元素数据结构
// Raw method_id_item.
struct MethodId {
uint16_t class_idx_; // index into type_ids_ array for defining class
uint16_t proto_idx_; // index into proto_ids_ array for method prototype
uint32_t name_idx_; // index into string_ids_ array for method name
private:
DISALLOW_COPY_AND_ASSIGN(MethodId);
};
- class_idx_: 定义该方法的类在type_ids中的索引
- proto_idx_: 方法的原型在proto_ids中的索引,uint16_t表示(0~65535),最多有65536个方法原型
- name_idx_: 方法的名字在string_ids中的索引
class_defs类定义列表
元素数据结构
// Raw class_def_item.
struct ClassDef {
uint16_t class_idx_; // index into type_ids_ array for this class
uint16_t pad1_; // padding = 0
uint32_t access_flags_;
uint16_t superclass_idx_; // index into type_ids_ array for superclass
uint16_t pad2_; // padding = 0
uint32_t interfaces_off_; // file offset to TypeList
uint32_t source_file_idx_; // index into string_ids_ for source file name
uint32_t annotations_off_; // file offset to annotations_directory_item
uint32_t class_data_off_; // file offset to class_data_item
uint32_t static_values_off_; // file offset to EncodedArray
......
}
class_idx_: 类在type_ids中的索引
access_flags_: 类的访问标志
superclass_idx_: 超类在type_ids中的索引
interfaces_off_: 接口列表在文件中的偏移量,如果没有接口,该值为0,否则该偏移量应该位于data段,采用type_list制定的格式
source_file_idx_: 文件(包含该类)名称在string_ids列表中的索引;或者该值为NO_INDEX,表示缺少信息
annotations_off_: 类的注解在文件中的偏移量,如果没有注解,该值为0,否则该偏移量应该位于data段,采用annotations_directory_item指定的格式
-
名称 格式 说明 static_fields_size uleb128 静态字段的数量 instance_fields_size uleb128 实例字段的数量 direct_methods_size uleb128 直接方法的数量 virtual_methods_size uleb128 虚方法的数量 static_fields encoded_field[static_fields_size] 静态字段,按在field_idx中的索引升序排序 instance_fields encoded_field[instance_fields_size] 实例字段,按在field_idx中的索引升序排序 direct_methods encoded_method[direct_methods_size] 直接方法,按在method_ids中的索引升序排序 virtual_methods encoded_method[virtual_methods_size] 虚方法,按在method_ids中的索引升序排序 class_data_off_: 类数据在文件中的偏移量,如果没有没数据(标记接口),该值为0;否则该值应该位于data段,采用class_data_item指定格式
名称 格式 说明 static_fields_size uleb128 静态字段的数量 instance_fields_size uleb128 实例字段的数量 direct_methods_size uleb128 直接方法的数量 virtual_methods_size uleb128 虚方法的数量 static_fields encoded_field[static_fields_size] 静态字段,按在field_idx中的索引升序排序 instance_fields encoded_field[instance_fields_size] 实例字段,按在field_idx中的索引升序排序 direct_methods encoded_method[direct_methods_size] 直接方法,按在method_ids中的索引升序排序 virtual_methods encoded_method[virtual_methods_size] 虚方法,按在method_ids中的索引升序排序 - encoded_field格式
名称 格式 说明 field_idx_diff uleb128 字段在field_ids列表中的索引;它的值为与列表中前一个元素的索引之差,第一个元素的值则直接表示 access_flags uleb128 字段的访问标志 - encoded_method 格式
名称 格式 说明 method_idx_diff uleb128 方法在method_ids列表中的索引;它的值为与列表中前一个元素的索引之差,第一个元素的值则直接表示 access_flags uleb128 方法的访问标志 code_off uleb128 方法的代码数据在文件中的偏移量,如果方法是abstract或者native,该值为0;否则该偏移量应该在data段中,采用code_item指定格式 - code_item
// Raw code_item. struct CodeItem { uint16_t registers_size_; // the number of registers used by this code // (locals + parameters) uint16_t ins_size_; // the number of words of incoming arguments to the method // that this code is for uint16_t outs_size_; // the number of words of outgoing argument space required // by this code for method invocation uint16_t tries_size_; // the number of try_items for this instance. If non-zero, // then these appear as the tries array just after the // insns in this instance. uint32_t debug_info_off_; // file offset to debug info stream uint32_t insns_size_in_code_units_; // size of the insns array, in 2 byte code units uint16_t insns_[1]; // actual array of bytecode. private: DISALLOW_COPY_AND_ASSIGN(CodeItem); };
- registers_size_: 代码使用的寄存器数量(包括本地变量以及参数)
- ins_size_: 方法入参字数
- outs_size_: 方法出参字数
- tries_size_: 该实例的try_items数目,如果非0,try_items出现在该实例的insns之后
- debug_info_off_: 调试信息在文件中的偏移,如果非0,该偏移量应该在data段,采用debug_info_item指定格式
- insns_size_in_code_units_: 指令列表的大小,以2个字节为单位
- insns_: 字节码实际数组
- try_item格式
// Raw try_item. struct TryItem { uint32_t start_addr_; // try代码块的起始地址 uint16_t insn_count_; // try代码块覆盖的16位代码单元的数量 uint16_t handler_off_; // 从关联的encoded_catch_hander_list开头部分到此条目的encoded_catch_handler的偏移量(以字节为单位) private: DISALLOW_COPY_AND_ASSIGN(TryItem); };
- encoded_catch_handler_list格式(在try_item列表之后)
名称 格式 说明 size uleb128 列表的大小 list encoded_catch_handler[handlers_size] 异常处理程序列表 - encoded_catch_handler格式
名称 格式 说明 size sleb128 捕获异常类型的数量,size为0表示捕获类型为“全部捕获”,而没有明确类型的捕获。size为2表示有两个明确类型的捕获,但没有“全部捕获”类型的捕获。size为-1表示有一个明确类型的捕获和一个“全部捕获”类型的捕获。 handlers encoded_type_addr_pair[abs(size)] 明确类型异常处理列表 catch_all_addr uleb128 “全部捕获”处理程序的字节码地址。只有当 size 为非正数时,此元素才会存在。 - encoded_type_addr_pair格式
名称 格式 说明 type_idx uleb128 要捕获的异常类型在type_ids列表中的索引 addr uleb128 对应的异常处理程序的字节码的地址 static_values_off_: 静态字段初始值列表在文件中的偏移量,如果没有该列表(或者静态字段使用0或者null进行初始化),该值为0;否则该值应该位于data段,采用encoded_array_item指定的格式
例子
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
final TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText(stringFromJNI());
}
});
testClassLoader();
}
private void testClassLoader() {
try {
ClassLoader cl = getClassLoader();
if (cl == null)
Log.i("wlb", "classload null");
while (cl != null) {
Log.i("wlb", cl.toString());
cl = cl.getParent();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
}
以MainActivity
为例说明
MainActivity
的class_def_item
class_defs_off : 332668 (0x05137c)
MainActivity在class_ids中的索引:1665
MainActivity的class_def_item偏移:0x05137c + 1665 * 32 = 0x05E39C
==============================================================================
0005e390 50 10 29 00 78 65 08 00 00 00 00 00 36 08 00 00 |P.).xe......6...|
0005e3a0 01 00 00 00 2b 06 00 00 00 00 00 00 9f 15 00 00 |....+...........|
0005e3b0 00 00 00 00 92 65 08 00 00 00 00 00 1e 02 00 00 |.....e..........|
==============================================================================
类在type_ids中的索引:0x0836 (0x1be74d 'Lcom/tracemaker/nativedynamiclinkdemo/MainActivity;')
类的访问标志: 0x00000001
超类在type_ids中的索引: 0x062b
文件(包含该类)名称在string_ids列表中的索引: 0x0000159f (0x001c25fe 'MainActivity.java')
类数据在文件中的偏移量: 0x00086592
MainActivity
的class_data
- 静态字段以及实例字段数量均为0
- 直接方法数为3 (
,
,testClassLoader
) - 虚方法数为2 (
onCreate
,stringFromJNI
)
MainActivity
的testClassLoader
方法
testClassLoader
对应的encoded_method十六进制数据06 02 8c 9a 55
- 方法在method_ids列表中的索引为: 0x439e (0x4397 + 0x1 + 0x6)
- 方法的访问标志: 0x0002
- 方法的代码数据在文件中的偏移: uleb128(8c 9a 55) = 0x154D0C
testClassLoader
方法的代码数据
- 代码使用的寄存器数量: 0x0005
- 入参数量: 0x0001
- 出参数量: 0x0001
- 方法的try_items项数: 0x0001
- 方法的指令数: 0x00000025(37个16位代码单元,74个字节)
- 方法的字节码
154d0c: |[154d0c] com.tracemaker.nativedynamiclinkdemo.MainActivity.testClassLoader:()V
154d1c: 6e10 9a43 0400 |0000: invoke-virtual {v4}, Lcom/tracemaker/nativedynamiclinkdemo/MainActivity;.getClassLoader:()Ljava/lang/ClassLoader; // method@439a
154d22: 0c00 |0003: move-result-object v0
154d24: 3900 0b00 |0004: if-nez v0, 000f // +000b
154d28: 1b02 244a 0000 |0006: const-string/jumbo v2, "wlb" // string@00004a24
154d2e: 1b03 e522 0000 |0009: const-string/jumbo v3, "classload null" // string@000022e5
154d34: 7120 113e 3200 |000c: invoke-static {v2, v3}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // method@3e11
154d3a: 3800 1500 |000f: if-eqz v0, 0024 // +0015
154d3e: 1b02 244a 0000 |0011: const-string/jumbo v2, "wlb" // string@00004a24
154d44: 6e10 4044 0000 |0014: invoke-virtual {v0}, Ljava/lang/Object;.toString:()Ljava/lang/String; // method@4440
154d4a: 0c03 |0017: move-result-object v3
154d4c: 7120 113e 3200 |0018: invoke-static {v2, v3}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // method@3e11
154d52: 6e10 0444 0000 |001b: invoke-virtual {v0}, Ljava/lang/ClassLoader;.getParent:()Ljava/lang/ClassLoader; // method@4404
154d58: 0c00 |001e: move-result-object v0
154d5a: 28f0 |001f: goto 000f // -0010
154d5c: 0d01 |0020: move-exception v1
154d5e: 6e10 0844 0100 |0021: invoke-virtual {v1}, Ljava/lang/Exception;.printStackTrace:()V // method@4408
154d64: 0e00 |0024: return-void
testClassLoader
方法try_item
- try代码块的起始地址: 0x0000(也就是0x154d1c)
- try代码块覆盖的16位代码单元的数量: 0x001e
- 关联的encoded_catch_hander_list开头部分到此条目的encoded_catch_handler的偏移量: 0x0001(1字节)
- encoded_catch_handler_list列表大小:0x01
- 捕获异常类型的数量:0x01
- 异常类型在type_ids列表中的索引: uleb128(e8 10)
异常类型字符串:
001bf410 15 4c 6a 61 76 61 2f 6c 61 6e 67 2f 45 78 63 65 |.Ljava/lang/Exce|
001bf420 70 74 69 6f 6e 3b 00 11 4c 6a 61 76 61 2f 6c 61 |ption;..Ljava/la|
- 异常处理程序的字节码的地址: 0x20(实际0x154d5c)
附录OAT文件格式
参考
- https://source.android.com/devices/tech/dalvik/dex-format?hl=zh-cn
- Hiding Behind ART