ART深入浅出5--了解Dex文件格式(2)

本文基于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(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的结构。

你可能感兴趣的:(ART揭秘,ART深入浅出,android)