1. 程式人生 > >ART深入淺出5--瞭解Dex檔案格式(2)

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