本文基于Android 7.1,不过因为从BSP拿到的版本略有区别,所以本文提到的源码未必与读者找到的源码完全一致。本文在提供源码片断时,将按照 <源码相对android工程的路径>:<行号> <类名> <函数名> 的方式,如果行号对不上,请参考类名和函数名来找到对应的源码。
本节介绍ClassDef的格式。ClassDef是Dex文件内部表示一个类的结构。包含了类的基本数据,如类的名称,访问级别,Field列表,Method列表等信息。
如何找到一个ClassDef?
在dex文件的header中,有一个class_defs_off_和class_defs_size_的成员,表示classdef的位置与ClassDef的数量。
art/runtime/dex_file.h:90
// Raw header_item.
struct Header {
....
uint32_t class_defs_size_; // number of ClassDefs
uint32_t class_defs_off_; // file offset of ClassDef array
....
};
如果要获取一个ClassDef数据,只需要用dex文件的base地址加上偏移即可:
ClassDef * pclassDef = ((ClassDef*)(base + header->class_defs_off_))[class_def_index];
这个base是包括了Header在内的整个dex文件的开头。
ClassDef的格式
ClassDef格式的定义如下:
art/runtime/dex_file.h:210
// 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_: 指向typeIds数组的索引,一个TypeId结构,包含的是类的名称。比如一个类Activity,
这个类的名称就是“android/app/Activity”; - access_flags_: 访问权限,是个掩码值,可以是kAccPublic/kAccPrivate/kAccProtected, kAccInterface/kAccAbstract/ kAccAnontation/kAccEnum等值,详细见 art/runtime/modifiers.h:24
- superclass_idx_ 这是超类的TypeId索引
- interfaces_off_ 如果该类implements了interface,interface的信息就会放在这里
- source_file_idx_:所属的源文件的stringId索引
- annotations_off_: 该类内部用到的声明的偏移
- class_data_off_: 类数据的偏移,类数据主要指field和method的定义
- static_values_off_: static final值的列表,只包括基本类型的值(如int, long, char, byte,float, double,short)
interfaces_off_相关的结构
DexFile::GetInterfaceList函数能够获得一个Interface列表,它的实现很简单:
art/runtime/dex_file.h:717
c++
const TypeList* GetInterfacesList(const ClassDef& class_def) const {
if (class_def.interfaces_off_ == 0) {
return nullptr;
} else {
const uint8_t* addr = begin_ + class_def.interfaces_off_;
return reinterpret_cast<const TypeList*>(addr);
}
}
TypeList是在上篇文章中,介绍Method的参数列表时提到过,这里也用到了这个结构:
art/runtime/dex_file.h:245
c++
// Raw type_item.
struct TypeItem {
uint16_t type_idx_; // index into type_ids section
....
};
// Raw type_list.
class TypeList {
...
private:
uint32_t size_; // size of the list, in entries
TypeItem list_[1]; // elements of the list
DISALLOW_COPY_AND_ASSIGN(TypeList);
};
可以很容易看出,interfaces_off_指出了一个interface类名索引的数组
Class_data_off_
class_data实际上是Field和Method的数据列表,但是因为数据长度不固定,所以不能用一个结构直接给出。class data使用了LEB128编码(详细参阅这里)
ART提供了一个ClassDataItemIterator的类,可以遍历其中的数据。它的重要函数有:
- void Next(): 获取下个数据
- bool HasNext():是否有下个数据,这个数据可能是field或者method
- HasNextStaticFiled/HasNextInstanceFiled, HasNextDirectMethod, HasNextVirtualMethod
- GetMemberIndex 获取Field在FieldIds中的索引,或者Method在MethodIds中的索引。用这个索引可以得到一个FieldId对象或者MethodId对象
- GetRawMemberAccessFlags 获取Field或者Method的访问标志
- GetFieldAccessFlags 获取Field的访问标志,实际上就是从GetRawMemberAccessFlags中获取和Field相关的标志
- GetMethodAccessFlags,与GetFieldAccessFlags类似,获取的是Method相关的标志
- GetMethodInvokeType,获取Method调用的方式。Method有以下几种调用方式:
- invoke-virtual: 调用虚函数专用
- invoke-direct: 调用private函数、构造函数专用
- invoke-super: 调用super函数专用
- invoke-static: 调用静态函数专用
- invoke-interface: 调用interface的函数时专用
- GetMethodCodeItem 获取mehtod的代码信息
实际上,上面只是一个封装类的用法,下面我们解析下class_data的各个部分及结构。我们可以用DexFile::GetClassData方法得到一个class_def的class data数据,这个数据包含下面几个部分:(注意,它们都是LEB128编码格式)
Header | LEB128 int | static field size | 静态域的大小,以字节为单位 |
LEB128 int | instance field size | 实例域的大小,以字节为单位 | |
LEB128 int | direct method size | 直接method的大小,包括super method, static method, private method, 构造函数等 | |
LEB128 int | virtual method size | 虚函数的大小 | |
Field 结构 | LEB128 int | field index delta | 相对于上个field index的差值 |
LEB128 unsigned int | access_flags | 访问标志 | |
Method 结构 | LEB128 int | Method index delta | 相对于上个Method index的差值 |
LEB128 unsigned int | access_flags | 访问标志 | LEB128 int |
code_off | 代码CodeItem数据结构的编译,相对于dex文件的 |
static_values_off
通过DexFile::GetEncodedStaticFieldValuesArray就可以获得一个uint8_t*指针,保存的就是static value的数据。这也是一个LEB128编码格式,ART同样提供了一个EncodedStaticFieldValueIterator类来迭代获取对应的值。它的结构是这样的
header | LEB128 unsigned int | array size | 以字节为单位的数组的长度 |
一个元素的结构 | uint8_t | value type | 值的类型,值必须是 kBoolean, kByte, kShort, kChar, kInt, kLong, kFloat, kDouble, kString, kType或者kNull中的一个 |
LEB128 int/long | value | 保存的具体value值 |
这个表的用法要和上面class_data内部的static field配合使用。在这里,一个static field对应一个 static value,它们按照顺序严格对应。如果:
- static field没有值,就会有一个kNull对应;
- static field是一个指向一个class,就会有一个kType对应
- static field是一个Object,需要通过new或者访问其他类的静态域来实现,这里则直接给出kNull值,而在类初始化的时候,才调用相关的代码来进行赋值。
annotations_off
这一部分是介绍java的声明的数据。声明有些时候是编译时用的,有些时候是运行时用的,因为结构复杂但是用的却不多,我们这里就不详细展开了,后面将专门开辟一篇文章介绍它。
至此,ClassDef的主体内容就介绍完成了,下面的文章,我将详细介绍CodeItem的结构。