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<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的結構。