ART載入OAT檔案的分析
本文對老羅部落格http://blog.csdn.net/luoshengyang/article/details/39307813 進行學習理解,針對android6.0系統原始碼,連個人理解帶複製貼上,總結的ART虛擬機器對OAT檔案的載入解析流程。
宣告:由於CSDN官方bug,文章編碼全部整合為亂碼,無法顯示程式碼,所以重新整合,程式碼用普通字型排版。
ART執行時提供了一個OatFile類,通過呼叫它的靜態成員函式Open可以在本程序中載入OAT檔案,它的實現如下所示(art/runtime/oat_file.cc):
OatFile* OatFile::Open(conststd::string& filename,
const std::string&location,
uint8_t* requested_base,
uint8_t* oat_file_begin,
bool executable,
const char*abs_dex_location,
std::string* error_msg){
CHECK(!filename.empty()) << location;
CheckLocation(location);
std::unique_ptr<OatFile> ret;
//Use dlopen only when flagged to do so, and when it's OK to load thingsexecutable.
//TODO: Also try when not executable? The issue here could be re-mapping aswritable (as
// !executable is a signthat we may want to patch), which may not be allowed for
// various reasons.
if(kUseDlopen && (kIsTargetBuild || kUseDlopenOnHost) &&executable) {
// Try to use dlopen. This may fail for various reasons, outlined below.We try dlopen, as
// this will register the oat file with the linker and allows libunwindto find our info.
ret.reset(OpenDlopen(filename, location, requested_base,abs_dex_location, error_msg));
if (ret.get() != nullptr) {
return ret.release();
}
if (kPrintDlOpenErrorMessage) {
LOG(ERROR) << "Failed to dlopen: " << *error_msg;
}
}
//If we aren't trying to execute, we just use our own ElfFile loader for a couplereasons:
//
//On target, dlopen may fail when compiling due to selinux restrictions oninstalld.
//
//We use our own ELF loader for Quick to deal with legacy apps that
//open a generated dex file by name, remove the file, then open
//another generated dex file with the same name. http://b/10614658
//
//On host, dlopen is expected to fail when cross compiling, so fall back toOpenElfFile.
//
//
//Another independent reason is the absolute placement of boot.oat. dlopen on thehost usually
//does honor the virtual address encoded in the ELF file only for ET_EXEC files,not ET_DYN.
std::unique_ptr<File>file(OS::OpenFileForReading(filename.c_str()));
if(file == nullptr) {
*error_msg = StringPrintf("Failed to open oat filename for reading:%s", strerror(errno));
return nullptr;
}
ret.reset(OpenElfFile(file.get(),location, requested_base, oat_file_begin, false, executable,
abs_dex_location,error_msg));
//It would be nice to unlink here. But we might have opened the file created bythe
//ScopedLock, which we better not delete to avoid races. TODO: Investigate how tofix the API
//to allow removal when we know the ELF must be borked.
return ret.release();
}
引數filename和location實際上是一樣的,指向要載入的OAT檔案。引數requested_base是一個可選引數,用來描述要載入的OAT檔案裡面的oatdata段要載入在的位置。引數executable表示要載入的OAT是不是應用程式的主執行檔案。
上述程式碼簡言之就是根據ART_USE_PORTABLE_COMPILER巨集選擇用OpenDlopen還是OpenElfFile方法載入OAT檔案。接下來分析這兩個函式。
首先分析OpenDlopen函式(art/runtime/oat_file.cc):
OatFile* OatFile::OpenDlopen(conststd::string& elf_filename,
conststd::string& location,
uint8_t*requested_base,
const char*abs_dex_location,
std::string* error_msg) {
std::unique_ptr<OatFile> oat_file(new OatFile(location, true));
bool success = oat_file->Dlopen(elf_filename, requested_base,abs_dex_location, error_msg);
if(!success) {
return nullptr;
}
return oat_file.release();
}
跳轉到Dlopen函式(一下程式碼為極度精簡版,原始碼請自行查閱art/runtime/oat_file.cc):
bool OatFile::Dlopen(const std::string&elf_filename, byte* requested_base) {
char* absolute_path = realpath(elf_filename.c_str(), NULL);
......
dlopen_handle_ = dlopen(absolute_path, RTLD_NOW);
......
begin_ = reinterpret_cast<byte*>(dlsym(dlopen_handle_,"oatdata"));
......
if(requested_base != NULL && begin_ != requested_base) {
......
return false;
}
end_ = reinterpret_cast<byte*>(dlsym(dlopen_handle_,"oatlastword"));
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
return Setup();
}
Dlopen首先通過動態連結器提供的dlopen函式將引數elf_filename指定的OAT檔案載入到記憶體中來,接著同樣是通過動態連結器提供的dlsym函式從載入的OAT檔案獲得oatdata和oatlastword的地址,分別儲存在當前正在處理的OatFile物件的成員變數begin_和end_中。oatdata的地址即為OAT檔案裡面的oatdata段載入到記憶體中的開始地址,而oatlastword的地址即為OAT檔案裡面的oatexec載入到記憶體中的結束地址。符號oatlastword本身也是屬於oatexec段的,它自己佔用了一個地址,也就是sizeof(uint32_t)個位元組,於是將前面得到的end_值加上sizeof(uint32_t),得到的才是oatexec段的結束地址。
實際上,上面得到的begin_值指向的是載入記憶體中的oatdata段的頭部,即OAT頭。這個OAT頭描述了OAT檔案所包含的DEX檔案的資訊,以及定義在這些DEX檔案裡面的類方法所對應的本地機器指令在記憶體的位置。另外,上面得到的end_是用來在解析OAT頭時驗證資料的正確性的。此外,如果引數requested_base的值不等於0,那麼就要求oatdata段必須要載入到requested_base指定的位置去,也就是上面得到的begin_值與requested_base值相等,否則的話就會出錯返回。
最後,OatFile類的成員函式Dlopen通過呼叫另外一個成員函式Setup來解析已經載入記憶體中的oatdata段,以獲得ART執行時所需要的更多資訊。我們分析完成OatFile類的靜態成員函式OpenElfFile之後,再來看OatFile類的成員函式Setup的實現。
接著分析OpenElfFile函式的實現,就是另外一種載入OAT檔案的函式(art/runtime/oat_file.cc):
OatFile* OatFile::OpenElfFile(File* file,
conststd::string& location,
uint8_t*requested_base,
uint8_t*oat_file_begin,
bool writable,
bool executable,
const char*abs_dex_location,
std::string*error_msg) {
std::unique_ptr<OatFile> oat_file(new OatFile(location,executable));
bool success = oat_file->ElfFileOpen(file, requested_base,oat_file_begin, writable, executable,
abs_dex_location, error_msg);
if(!success) {
CHECK(!error_msg->empty());
return nullptr;
}
return oat_file.release();
}
跳轉到ElfFileOpen函式:
bool OatFile::ElfFileOpen(File* file,uint8_t* requested_base, uint8_t* oat_file_begin,
bool writable, boolexecutable,
const char*abs_dex_location,
std::string*error_msg) {
//TODO: rename requested_base to oat_data_begin
elf_file_.reset(ElfFile::Open(file, writable,/*program_header_only*/true, error_msg,
oat_file_begin));
......
boolloaded = elf_file_->Load(executable, error_msg);
......
begin_ =elf_file_->FindDynamicSymbolAddress("oatdata");
......
end_= elf_file_->FindDynamicSymbolAddress("oatlastword");
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
......
returnSetup(abs_dex_location, error_msg);}
bool OatFile::ElfFileOpen(File* file,uint8_t* requested_base, uint8_t* oat_file_begin,
bool writable, boolexecutable,
const char*abs_dex_location,
std::string*error_msg) {
//TODO: rename requested_base to oat_data_begin
elf_file_.reset(ElfFile::Open(file, writable,/*program_header_only*/true, error_msg,
oat_file_begin));
......
boolloaded = elf_file_->Load(executable, error_msg);
......
begin_ =elf_file_->FindDynamicSymbolAddress("oatdata");
......
end_= elf_file_->FindDynamicSymbolAddress("oatlastword");
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
......
return Setup(abs_dex_location, error_msg);}
bool OatFile::ElfFileOpen(File* file,uint8_t* requested_base, uint8_t* oat_file_begin,
bool writable, boolexecutable,
const char* abs_dex_location,
std::string*error_msg) {
//TODO: rename requested_base to oat_data_begin
elf_file_.reset(ElfFile::Open(file, writable,/*program_header_only*/true, error_msg,
oat_file_begin));
......
boolloaded = elf_file_->Load(executable, error_msg);
......
begin_ =elf_file_->FindDynamicSymbolAddress("oatdata");
......
end_= elf_file_->FindDynamicSymbolAddress("oatlastword");
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
......
return Setup(abs_dex_location, error_msg);}
OatFile類的靜態成員函式OpenElfFile的實現與前面分析的成員函式Dlopen是很類似的,唯一不同的是前者通過ElfFile類來手動載入引數file指定的OAT檔案,實際上就是按照ELF檔案格式來解析引數file指定的OAT檔案,並且將檔案裡面的oatdata段和oatexec段載入到記憶體中來。
接下來分析Dlopen和OpenElfFile流程上都要呼叫的Setup(abs_dex_location,error_msg)函式的實現(art/runtime/oat_file.cc),函式太長分為三段分析,首先看第一段:
bool OatFile::Setup(const char*abs_dex_location, std::string* error_msg) {
if(!GetOatHeader().IsValid()) {
std::string cause = GetOatHeader().GetValidationErrorMessage();
*error_msg = StringPrintf("Invalid oat header for '%s': %s",GetLocation().c_str(),
cause.c_str());
return false;
}
const uint8_t* oat = Begin();
oat+= sizeof(OatHeader);
if(oat > End()) {
*error_msg = StringPrintf("In oat file '%s' found truncatedOatHeader", GetLocation().c_str());
return false;
}
oat+= GetOatHeader().GetKeyValueStoreSize();
if(oat > End()) {
*error_msg = StringPrintf("In oat file '%s' found truncatedvariable-size data: "
"%p + %zd +%ud <= %p", GetLocation().c_str(),
Begin(),sizeof(OatHeader), GetOatHeader().GetKeyValueStoreSize(),
End());
return false;
}
其中,GetOatHeader、Begin和End函式的實現(art/runtime/oat_file.cc),如下所示:
const OatHeader&OatFile::GetOatHeader() const {
return *reinterpret_cast<const OatHeader*>(Begin());
}
const uint8_t* OatFile::Begin() const {
CHECK(begin_ != nullptr);
return begin_;
}
const uint8_t* OatFile::End() const {
CHECK(end_ != nullptr);
return end_;
}
這三個函式主要是涉及到了OatFile類的兩個成員變數begin_和end_,由上邊對兩種載入OAT檔案方法的分析,它們分別是OAT檔案裡面的oatdata段開始地址和oatexec段的結束地址。
通過OatFile類的成員函式GetOatHeader可以清楚地看到,OAT檔案裡面的oatdata段的開始儲存著一個OAT頭(art/runtime/oat.h):
class PACKED(4) OatHeader {
public:
......
private:
uint8_t magic_[4];
uint8_t version_[4];
uint32_t adler32_checksum_;
InstructionSet instruction_set_;
uint32_t dex_file_count_;
uint32_t executable_offset_;
uint32_t interpreter_to_interpreter_bridge_offset_;
uint32_t interpreter_to_compiled_code_bridge_offset_;
uint32_t jni_dlsym_lookup_offset_;
uint32_t portable_resolution_trampoline_offset_;
uint32_t portable_to_interpreter_bridge_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;
uint32_t image_file_location_oat_checksum_;
uint32_t image_file_location_oat_data_begin_;
uint32_t image_file_location_size_;
uint8_t image_file_location_data_[0]; // note variable width data at end
......
};
類OatHeader的各個成員變數的含義如下所示:
magic: 標誌OAT檔案的一個魔數,等於‘oat\n’。
version: OAT檔案版本號,目前的值等於‘007、0’。
adler32_checksum_: OAT頭部檢驗和。
instruction_set_: 本地機指令集,有四種取值,分別為 kArm(1)、kThumb2(2)、kX86(3)和kMips(4)。
dex_file_count_: OAT檔案包含的DEX檔案個數。
executable_offset_: oatexec段開始位置與oatdata段開始位置的偏移值。
interpreter_to_interpreter_bridge_offset_和interpreter_to_compiled_code_bridge_offset_:ART執行時在啟動的時候,可以通過-Xint選項指定所有類的方法都是解釋執行的,這與傳統的虛擬機器使用直譯器來執行類方法差不多。同時,有些類方法可能沒有被翻譯成本地機器指令,這時候也要求對它們進行解釋執行。這意味著解釋執行的類方法在執行的過程中,可能會呼叫到另外一個也是解釋執行的類方法,也可能呼叫到另外一個按本地機器指令執行的類方法中。OAT檔案在內部提供有兩段trampoline程式碼,分別用來從直譯器呼叫另外一個也是通過直譯器來執行的類方法和從直譯器呼叫另外一個按照本地機器執行的類方法。這兩段trampoline程式碼的偏移位置就儲存在成員變數 interpreter_to_interpreter_bridge_offset_和interpreter_to_compiled_code_bridge_offset_。
jni_dlsym_lookup_offset_: 類方法在執行的過程中,如果要呼叫另外一個方法是一個JNI函式,那麼就要通過存在放置jni_dlsym_lookup_offset_的一段trampoline程式碼來呼叫。
portable_resolution_trampoline_offset_和quick_resolution_trampoline_offset_: 用來在執行時解析還未連結的類方法的兩段trampoline程式碼。其中,portable_resolution_trampoline_offset_指向的trampoline程式碼用於Portable型別的Backend生成的本地機器指令,而quick_resolution_trampoline_offset_用於Quick型別的Backend生成的本地機器指令。
portable_to_interpreter_bridge_offset_和quick_to_interpreter_bridge_offset_: 與interpreter_to_interpreter_bridge_offset_和interpreter_to_compiled_code_bridge_offset_的作用剛好相反,用來在按照本地機器指令執行的類方法中呼叫解釋執行的類方法的兩段trampoline程式碼。其中,portable_to_interpreter_bridge_offset_用於Portable型別的Backend生成的本地機器指令,而quick_to_interpreter_bridge_offset_用於Quick型別的Backend生成的本地機器指令。
由於每一個應用程式都會依賴於boot.art檔案,因此為了節省由打包在應用程式裡面的classes.dex生成的OAT檔案的體積,上述interpreter_to_interpreter_bridge_offset_、interpreter_to_compiled_code_bridge_offset_、jni_dlsym_lookup_offset_、portable_resolution_trampoline_offset_、portable_to_interpreter_bridge_offset_、quick_resolution_trampoline_offset_和quick_to_interpreter_bridge_offset_七個成員變數指向的trampoline程式碼段只存在於boot.art檔案中。換句話說,在由打包在應用程式裡面的classes.dex生成的OAT檔案的oatdata段頭部中,上述七個成員變數的值均等於0。
image_file_location_data_: 用來建立Image空間的檔案的路徑的在記憶體中的地址。
image_file_location_size_: 用來建立Image空間的檔案的路徑的大小。
image_file_location_oat_data_begin_: 用來建立Image空間的OAT檔案的oatdata段在記憶體的位置。
image_file_location_oat_checksum_: 用來建立Image空間的OAT檔案的檢驗和。
上述四個成員變數記錄了一個OAT檔案所依賴的用來建立Image空間檔案以及建立這個Image空間檔案所使用的OAT檔案的相關資訊。
通過OatFile類的成員函式Setup的第一部分程式碼的分析,我們就知道了,OAT檔案的oatdata段在最開始儲存著一個OAT頭,如圖所示:
我們接著再看OatFile類的成員函式Setup的第二部分程式碼:
oat+= GetOatHeader().GetKeyValueStoreSize();
if(oat > End()) {
*error_msg = StringPrintf("In oat file '%s' found truncatedvariable-size data: "
"%p + %zd +%ud <= %p", GetLocation().c_str(),
Begin(),sizeof(OatHeader), GetOatHeader().GetKeyValueStoreSize(),
End());
return false;
}
呼叫OatFile類的成員函式GetOatHeader獲得的是正在開啟的OAT檔案的頭部OatHeader,通過呼叫它的成員函式GetImageFileLocationSize獲得的是正在開啟的OAT依賴的Image空間檔案的路徑大小。變數oat最開始的時候指向oatdata段的開始位置。讀出OAT頭之後,變數oat就跳過了OAT頭。由於正在開啟的OAT檔案引用的Image空間檔案路徑儲存在緊接著OAT頭的地方。因此,將Image空間檔案的路徑大小增加到變數oat去後,就相當於是跳過了儲存Image空間檔案路徑的位置。
通過OatFile類的成員函式Setup的第二部分程式碼的分析,我們就知道了,緊接著在OAT頭後面的是Image空間檔案路徑,如圖所示:
我們接著再看OatFile類的成員函式Setup的第三部分程式碼:
for(size_t i = 0; i < GetOatHeader().GetDexFileCount(); i++) {
size_t dex_file_location_size = *reinterpret_cast<constuint32_t*>(oat);
......
oat += sizeof(dex_file_location_size);
......
const char* dex_file_location_data = reinterpret_cast<constchar*>(oat);
oat += dex_file_location_size;
......
std::string dex_file_location(dex_file_location_data,dex_file_location_size);
uint32_t dex_file_checksum = *reinterpret_cast<constuint32_t*>(oat);
oat += sizeof(dex_file_checksum);
......
uint32_t dex_file_offset = *reinterpret_cast<constuint32_t*>(oat);
......
oat += sizeof(dex_file_offset);
......
const uint8_t* dex_file_pointer = Begin() + dex_file_offset;
if (!DexFile::IsMagicValid(dex_file_pointer)) {
......
return false;
}
if (!DexFile::IsVersionValid(dex_file_pointer)) {
......
return false;
}
const DexFile::Header* header = reinterpret_cast<constDexFile::Header*>(dex_file_pointer);
const uint32_t* methods_offsets_pointer = reinterpret_cast<constuint32_t*>(oat);
oat += (sizeof(*methods_offsets_pointer) *header->class_defs_size_);
......
oat_dex_files_.Put(dex_file_location, new OatDexFile(this,
dex_file_location,
dex_file_checksum,
dex_file_pointer,
methods_offsets_pointer));
}
return true;
}
這部分程式碼用來獲得包含在oatdata段的DEX檔案描述資訊。每一個DEX檔案記錄在oatdata段的描述資訊包括:
1. DEX檔案路徑大小,儲存在變數dex_file_location_size中;
2. DEX檔案路徑,儲存在變數dex_file_location_data中;
3. DEX檔案檢驗和,儲存在變數dex_file_checksum中;
4. DEX檔案內容在oatdata段的偏移,儲存在變數dex_file_offset中;
5. DEX檔案包含的類的本地機器指令資訊偏移陣列,儲存在變數methods_offsets_pointer中;
在上述五個資訊中,最重要的就是第4個和第5個資訊了。
通過第4個資訊,我們可以在oatdata段中找到對應的DEX檔案的內容。DEX檔案最開始部分是一個DEX檔案頭,上述程式碼通過檢查DEX檔案頭的魔數和版本號來確保變數dex_file_offset指向的位置確實是一個DEX檔案。
通過第5個資訊我們可以找到DEX檔案裡面的每一個類方法對應的本地機器指令。這個陣列的大小等於header->class_defs_size_,即DEX檔案裡面的每一個類在陣列中都對應有一個偏移值。這裡的header指向的是DEX檔案頭,它的class_defs_size_描述了DEX檔案包含的類的個數。在DEX檔案中,每一個類都是有一個從0開始的編號,該編號就是用來索引到上述陣列的,從而獲得對應的類所有方法的本地機器指令資訊。
最後,上述得到的每一個DEX檔案的資訊都被封裝在一個OatDexFile物件中,以便以後可以直接訪問。如果我們使用OatDexFile來描述每一個DEX檔案的描述資訊,那麼就可以通過圖看到這些描述資訊在oatdata段的位置:
為了進一步理解包含在oatdata段的DEX檔案描述資訊,我們繼續看OatDexFile類的建構函式的實現,如下所示:
OatFile::OatDexFile::OatDexFile(const OatFile* oat_file,
const std::string& dex_file_location,
const std::string& canonical_dex_file_location,
uint32_t dex_file_location_checksum,
const uint8_t* dex_file_pointer,
const uint32_t* oat_class_offsets_pointer)
: oat_file_(oat_file),
dex_file_location_(dex_file_location),
canonical_dex_file_location_(canonical_dex_file_location),
dex_file_location_checksum_(dex_file_location_checksum),
dex_file_pointer_(dex_file_pointer),
oat_class_offsets_pointer_(oat_class_offsets_pointer) {}
OatDexFile類它將DEX檔案描述息儲存在相應的成員變數中。通過這些資訊,我們就可以獲得包含在該DEX檔案裡面的類的所有方法的本地機器指令資訊。
例如,通過呼叫OatDexFile類的成員函式GetOatClass可以獲得指定類的所有方法的本地機器指令資訊:
- const OatFile::OatClass* OatFile::OatDexFile::GetOatClass(uint16_t class_def_index) const {
- uint32_t oat_class_offset = oat_class_offsets_pointer_[class_def_index];
- const byte* oat_class_pointer = oat_file_->Begin() + oat_class_offset;
- CHECK_LT(oat_class_pointer, oat_file_->End()) << oat_file_->GetLocation();
- mirror::Class::Status status = *reinterpret_cast<const mirror::Class::Status*>(oat_class_pointer);
- const byte* methods_pointer = oat_class_pointer + sizeof(status);
- CHECK_LT(methods_pointer, oat_file_->End()) << oat_file_->GetLocation();
- returnnew OatClass(oat_file_,
- status,
- reinterpret_cast<const OatMethodOffsets*>(methods_pointer));
- }
引數class_def_index表示要查詢的目標類的編號。這個編號用作陣列oat_class_offsets_pointer_(即前面描述的methods_offsets_pointer陣列)的索引,就可以得到一個偏移位置oat_class_offset。這個偏移位置是相對於OAT檔案的oatdata段的,因此將該偏移值加上OAT檔案的oatdata段的開始位置後,就可以得到目標類的所有方法的本地機器指令資訊。這些資訊的佈局如圖5所示:
在OAT檔案中,每一個DEX檔案包含的每一個類的描述資訊都通過一個OatClass物件來描述。為了方便描述,我們稱之為OAT類。我們通過OatClass類的建構函式來理解它的作用,如下所示:
- OatFile::OatClass::OatClass(const OatFile* oat_file,
- mirror::Class::Status status,
- const OatMethodOffsets* methods_pointer)
- : oat_file_(oat_file), status_(status), methods_pointer_(methods_pointer) {}
引數oat_file描述的是宿主OAT檔案,引數status描述的是OAT類狀態,引數methods_pointer是一個數組,描述的是OAT類的各個方法的資訊,它們被分別儲存在OatClass類的相應成員變數中。通過這些資訊,我們就可以獲得包含在該DEX檔案裡面的類的所有方法的本地機器指令資訊。
例如,通過呼叫OatClass類的成員函式GetOatMethod可以獲得指定類方法的本地機器指令資訊(注意程式碼與老羅部落格區別):
const OatFile::OatMethod OatFile::OatClass::GetOatMethod(uint32_t method_index) const {const OatMethodOffsets* oat_method_offsets = GetOatMethodOffsets(method_index);
if (oat_method_offsets == nullptr) {
return OatMethod(nullptr, 0);
}
if (oat_file_->IsExecutable() ||
Runtime::Current() == nullptr || // This case applies for oatdump.
Runtime::Current()->IsAotCompiler()) {
return OatMethod(oat_file_->Begin(), oat_method_offsets->code_offset_);
}
// We aren't allowed to use the compiled code. We just force it down the interpreted / jit
// version.
return OatMethod(oat_file_->Begin(), 0);
}
引數method_index描述的目標方法在類中的編號,用這個編號作為索引,就可以在OatClass類的成員變數methods_pointer_指向的一個數組中找到目標方法的本地機器指令資訊。這些本地機器指令資訊封裝在一個OatMethod物件,它們在OAT檔案的佈局如圖6下所示:
為了進一步理解OatMethod的作用,我們繼續看它的建構函式的實現,如下所示(art/runtime/oat_file.h):
OatMethod(const uint8_t* base, const uint32_t code_offset)
: begin_(base), code_offset_(code_offset) {
}
引數base描述的是OAT檔案的OAT頭在記憶體的位置,而引數code_offset描述的是類方法的本地機器指令相對OAT頭的偏移位置。將這兩者相加,就可以得到一個類方法的本地機器指令在記憶體的位置。我們可以通過呼叫OatMethod類的成員函式GetCode來獲得這個結果。
OatMethod類的成員函式GetQuickCode的實現如下所示(art/runtime/oat_file.h,老羅版本是在.cc檔案中,注意區別):
const void* GetQuickCode() const {
return GetOatPointer<const void*>(code_offset_);
}
OatMethod類的成員函式呼叫另外一個成員函式GetOatPointer來獲得一個類方法的本地機器指令在記憶體的位置。
OatMethod類的成員函式GetOatPointer的實現如下所示(art/runtime/oat_file.h,注意區別):
-
template<class T>
T GetOatPointer(uint32_t offset) const {
if (offset == 0) {
return nullptr;
}
return reinterpret_cast<T>(begin_ + offset);
}
我們從左往右來看圖。首先是根據類簽名信息從包含在OAT檔案裡面的DEX檔案中查詢目標Class的編號,然後再根據這個編號找到在OAT檔案中找到對應的OatClass。接下來再根據方法簽名從包含在OAT檔案裡面的DEX檔案中查詢目標方法的編號,然後再根據這個編號在前面找到的OatClass中找到對應的OatMethod。有了這個OatMethod之後,我們就根據它的成員變數begin_和code_offset_找到目標類方法的本地機器指令了。其中,從DEX檔案中根據簽名找到類和方法的編號要求對DEX檔案進行解析,這就需要利用Dalvik虛擬機器的知識了。