Android運行時ART加載OAT文件的過程分析_第1頁
Android運行時ART加載OAT文件的過程分析_第2頁
Android運行時ART加載OAT文件的過程分析_第3頁
Android運行時ART加載OAT文件的過程分析_第4頁
Android運行時ART加載OAT文件的過程分析_第5頁
已閱讀5頁,還剩21頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

Android運行時ART加載OAT文件的過程分析在前面一文中,我們介紹了Android運行時ART,它的核心是OAT文件。OAT文件是一種Android私有ELF文件格式,它不僅包含有從DEX文件翻譯而來的本地機器指令,還包含有原來的DEX文件內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART里面運行,也就是我們不需要改變原來的APK編程接口。本文我們通過OAT文件的加載過程分析OAT文件的結構,為后面分析ART的工作原理打基礎。由于OAT文件本質上是一個ELF文件,因此在最外層它具有一般ELF文件的結構,例如它有標準的ELF文件頭以及通過段(Section)來描述文件內容。關于ELF文件的更多知識,可以參考維基百科:。作為Android私有的一種ELF文件,OAT文件包含有兩個特殊的段oatdata和oatexec,前者包含有用來生成本地機器指令的dex文件內容,后者包含有生成的本地機器指令,它們之間的關系通過儲存在oatdata段前面的oat頭部描述。此外,在OAT文件的dynamic段,導出了三個符號oatdata、oatexec和oatlastword,它們的值就是用來界定oatdata段和oatexec段的起止位置的。其中,[oatdata,oatexec-1]描述的是oatdata段的起止位置,而[oatexec,oatlastword+3]描述的是oatexec的起止位置。要完全理解OAT的文件格式,除了要理解本文即將要分析的OAT加載過程之外,還需要掌握接下來文章分析的類和方法查找過程。在分析OAT文件的加載過程之前,我們需要簡單介紹一下OAT是如何產生的。如前面一文所示,APK在安裝的過程中,會通過dex2oat工具生成一個OAT文件:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片staticvoidrun_dex2oat(intzip_fd,intoat_fd,constchar*input_file_name,constchar*output_file_name,constchar*dexopt_flags){staticconstchar*DEX2OAT_BIN="/system/bin/dex2oat";staticconstintMAX_INT_LEN=12;//'-'+10dig+'\0'-OR-0x+8digcharzip_fd_arg[strlen("--zip-fd=")+MAX_INT_LEN];charzip_location_arg[strlen("--zip-location=")+PKG_PATH_MAX];charoat_fd_arg[strlen("--oat-fd=")+MAX_INT_LEN];charoat_location_arg[strlen("--oat-name=")+PKG_PATH_MAX];sprintf(zip_fd_arg,"--zip-fd=%d",zip_fd);sprintf(zip_location_arg,"--zip-location=%s",input_file_name);sprintf(oat_fd_arg,"--oat-fd=%d",oat_fd);sprintf(oat_location_arg,"--oat-location=%s",output_file_name);ALOGV("Running%sin=%sout=%s\n",DEX2OAT_BIN,input_file_name,output_file_name);execl(DEX2OAT_BIN,DEX2OAT_BIN,zip_fd_arg,zip_location_arg,oat_fd_arg,oat_location_arg,(char*)NULL);ALOGE("execl(%s)failed:%s\n",DEX2OAT_BIN,strerror(errno));}這個函數定義在文件frameworks/native/cmds/installd/commands.c中。其中,參數zip_fd和oat_fd都是打開文件描述符,指向的分別是正在安裝的APK文件和要生成的OAT文件。OAT文件的生成過程主要就是涉及到將包含在APK里面的classes.dex文件的DEX字節(jié)碼翻譯成本地機器指令。這相當于是編寫一個輸入文件為DEX、輸出文件為OAT的編譯器。這個編譯器是基于LLVM編譯框架開發(fā)的。編譯器的工作原理比較高大上,所幸的是它不會影響到我們接下來的分析,因此我們就略過DEX字節(jié)碼翻譯成本地機器指令的過程,假設它很愉快地完成了。APK安裝過程中生成的OAT文件的輸入只有一個DEX文件,也就是來自于打包在要安裝的APK文件里面的classes.dex文件。實際上,一個OAT文件是可以由若干個DEX生成的。這意味著在生成的OAT文件的oatdata段中,包含有多個DEX文件。那么,在什么情況下,會生成包含多個DEX文件的OAT文件呢?從前面一文可以知道,當我們選擇了ART運行時時,Zygote進程在啟動的過程中,會調用libart.so里面的函數JNI_CreateJavaVM來創(chuàng)建一個ART虛擬機。函數JNI_CreateJavaVM的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片extern"C"jintJNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){constJavaVMInitArgs*args=static_cast<JavaVMInitArgs*>(vm_args);if(IsBadJniVersion(args->version)){LOG(ERROR)<<"BadJNIversionpassedtoCreateJavaVM:"<<args->version;returnJNI_EVERSION;}Runtime::Optionsoptions;for(inti=0;i<args->nOptions;++i){JavaVMOption*option=&args->options[i];options.push_back(std::make_pair(std::string(option->optionString),option->extraInfo));}boolignore_unrecognized=args->ignoreUnrecognized;if(!Runtime::Create(options,ignore_unrecognized)){returnJNI_ERR;}Runtime*runtime=Runtime::Current();boolstarted=runtime->Start();if(!started){deleteThread::Current()->GetJniEnv();deleteruntime->GetJavaVM();LOG(WARNING)<<"CreateJavaVMfailed";returnJNI_ERR;}*p_env=Thread::Current()->GetJniEnv();*p_vm=runtime->GetJavaVM();returnJNI_OK;}這個函數定義在文件art/runtime/jni_internal.cc中。參數vm_args用作ART虛擬機的啟動參數,它被轉換為一個JavaVMInitArgs對象后,再按照Key-Value的組織形式保存一個Options向量中,并且以該向量作為參數傳遞給Runtime類的靜態(tài)成員函數Create。Runtime類的靜態(tài)成員函數Create負責在進程中創(chuàng)建一個ART虛擬機。創(chuàng)建成功后,就調用Runtime類的另外一個靜態(tài)成員函數Start啟動該ART虛擬機。注意,這個創(chuàng)建ART虛擬的動作只會在Zygote進程中執(zhí)行,SystemServer系統(tǒng)進程以及Android應用程序進程的ART虛擬機都是直接從Zygote進程fork出來共享的。這與Dalvik虛擬機的創(chuàng)建方式是完全一樣的。接下來我們就重點分析Runtime類的靜態(tài)成員函數Create,它的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片boolRuntime::Create(constOptions&options,boolignore_unrecognized){//TODO:acquireastaticmutexonRuntimetoavoidracing.if(Runtime::instance_!=NULL){returnfalse;}InitLogging(NULL);//CallsLocks::Init()asasideeffect.instance_=newRuntime;if(!instance_->Init(options,ignore_unrecognized)){deleteinstance_;instance_=NULL;returnfalse;}returntrue;}這個函數定義在文件art/runtime/runtime.cc中。instance_是Runtime類的靜態(tài)成員變量,它指向進程中的一個Runtime單例。這個Runtime單例描述的就是當前進程的ART虛擬機實例。函數首先判斷當前進程是否已經創(chuàng)建有一個ART虛擬機實例了。如果有的話,函數就立即返回。否則的話,就創(chuàng)建一個ART虛擬機實例,并且保存在Runtime類的靜態(tài)成員變量instance_中,最后調用Runtime類的成員函數Init對該新創(chuàng)建的ART虛擬機進行初始化。Runtime類的成員函數Init的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片boolRuntime::Init(constOptions&raw_options,boolignore_unrecognized){......UniquePtr<ParsedOptions>options(ParsedOptions::Create(raw_options,ignore_unrecognized));......heap_=newgc::Heap(options->heap_initial_size_,options->heap_growth_limit_,options->heap_min_free_,options->heap_max_free_,options->heap_target_utilization_,options->heap_maximum_size_,options->image_,options->is_concurrent_gc_enabled_,options->parallel_gc_threads_,options->conc_gc_threads_,options->low_memory_mode_,options->long_pause_log_threshold_,options->long_gc_log_threshold_,options->ignore_max_footprint_);......java_vm_=newJavaVMExt(this,options.get());......Thread*self=Thread::Attach("main",false,NULL,false);......if(GetHeap()->GetContinuousSpaces()[0]->IsImageSpace()){class_linker_=ClassLinker::CreateFromImage(intern_table_);}else{......class_linker_=ClassLinker::CreateFromCompiler(*options->boot_class_path_,intern_table_);}......returntrue;}這個函數定義在文件art/runtime/runtime.cc中。Runtime類的成員函數Init首先調用ParsedOptions類的靜態(tài)成員函數Create對ART虛擬機的啟動參數raw_options進行解析。解析后得到的參數保存在一個ParsedOptions對象中,接下來就根據這些參數一個ART虛擬機堆。ART虛擬機堆使用一個Heap對象來描述。創(chuàng)建好ART虛擬機堆后,Runtime類的成員函數Init接著又創(chuàng)建了一個JavaVMExt實例。這個JavaVMExt實例最終是要返回給調用者的,使得調用者可以通過該JavaVMExt實例來和ART虛擬機交互。再接下來,Runtime類的成員函數Init通過Thread類的成員函數Attach將當前線程作為ART虛擬機的主線程,使得當前線程可以調用ART虛擬機提供的JNI接口。Runtime類的成員函數GetHeap返回的便是當前ART虛擬機的堆,也就是前面創(chuàng)建的ART虛擬機堆。通過調用Heap類的成員函數GetContinuousSpaces可以獲得堆里面的連續(xù)空間列表。如果這個列表的第一個連續(xù)空間是一個Image空間,那么就調用ClassLinker類的靜態(tài)成員函數CreateFromImage來創(chuàng)建一個ClassLinker對象。否則的話,上述ClassLinker對象就要通過ClassLinker類的另外一個靜態(tài)成員函數CreateFromCompiler來創(chuàng)建。創(chuàng)建出來的ClassLinker對象是后面ART虛擬機加載加載Java類時要用到的。后面我們分析ART虛擬機的垃圾收集機制時會看到,ART虛擬機的堆包含有三個連續(xù)空間和一個不連續(xù)空間。三個連續(xù)空間分別用來分配不同的對象。當第一個連續(xù)空間不是Image空間時,就表明當前進程不是Zygote進程,而是安裝應用程序時啟動的一個dex2oat進程。安裝應用程序時啟動的dex2oat進程也會在內部創(chuàng)建一個ART虛擬機,不過這個ART虛擬機是用來將DEX字節(jié)碼編譯成本地機器指令的,而Zygote進程創(chuàng)建的ART虛擬機是用來運行應用程序的。接下來我們主要分析ParsedOptions類的靜態(tài)成員函數Create和ART虛擬機堆Heap的構造函數,以便可以了解ART虛擬機的啟動參數解析過程和ART虛擬機的堆創(chuàng)建過程。ParsedOptions類的靜態(tài)成員函數Create的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片Runtime::ParsedOptions*Runtime::ParsedOptions::Create(constOptions&options,boolignore_unrecognized){UniquePtr<ParsedOptions>parsed(newParsedOptions());constchar*boot_class_path_string=getenv("BOOTCLASSPATH");if(boot_class_path_string!=NULL){parsed->boot_class_path_string_=boot_class_path_string;}......parsed->is_compiler_=false;......for(size_ti=0;i<options.size();++i){conststd::stringoption(options[i].first);......if(StartsWith(option,"-Xbootclasspath:")){parsed->boot_class_path_string_=option.substr(strlen("-Xbootclasspath:")).data();}elseif(option=="bootclasspath"){parsed->boot_class_path_=reinterpret_cast<conststd::vector<constDexFile*>*>(options[i].second);}elseif(StartsWith(option,"-Ximage:")){parsed->image_=option.substr(strlen("-Ximage:")).data();}elseif(......){......}elseif(option=="compiler"){parsed->is_compiler_=true;}else{......}}......if(!parsed->is_compiler_&&parsed->image_.empty()){parsed->image_+=GetAndroidRoot();parsed->image_+="/framework/boot.art";}......returnparsed.release();}這個函數定義在文件art/runtime/runtime.cc中。ART虛擬機的啟動參數比較多,這里我們只關注兩個:-Xbootclasspath、-Ximage和compiler。參數-Xbootclasspath用來指定啟動類路徑。如果沒有指定啟動類路徑,那么默認的啟動類路徑就通過環(huán)境變量BOOTCLASSPATH來獲得。參數-Ximage用來指定ART虛擬機所使用的Image文件。這個Image是用來啟動ART虛擬機的。參數compiler用來指定當前要創(chuàng)建的ART虛擬機是用來將DEX字節(jié)碼編譯成本地機器指令的。如果沒有指定Image文件,并且當前創(chuàng)建的ART虛擬機又不是用來編譯DEX字節(jié)碼的,那么就將該Image文件指定為設備上的/system/framework/boot.art文件。我們知道,system分區(qū)的文件都是在制作ROM時打包進去的。這樣上述代碼的邏輯就是說,如果沒有指定Image文件,那么將system分區(qū)預先準備好的framework/boot.art文件作為Image文件來啟動ART虛擬機。不過,/system/framework/boot.art文件可能是不存在的。在這種情況下,就需要生成一個新的Image文件。這個Image文件就是一個包含了多個DEX文件的OAT文件。接下來通過分析ART虛擬機堆的創(chuàng)建過程就會清楚地看到這一點。Heap類的構造函數的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片Heap::Heap(size_tinitial_size,size_tgrowth_limit,size_tmin_free,size_tmax_free,doubletarget_utilization,size_tcapacity,conststd::string&original_image_file_name,boolconcurrent_gc,size_tparallel_gc_threads,size_tconc_gc_threads,boollow_memory_mode,size_tlong_pause_log_threshold,size_tlong_gc_log_threshold,boolignore_max_footprint):......{......std::stringimage_file_name(original_image_file_name);if(!image_file_name.empty()){space::ImageSpace*image_space=space::ImageSpace::Create(image_file_name);......AddContinuousSpace(image_space);......}......}這個函數定義在文件art/runtime/gc/heap.cc中。ART虛擬機堆的詳細創(chuàng)建過程我們在后面分析ART虛擬機的垃圾收集機制時再分析,這里只關注與Image文件相關的邏輯。參數original_image_file_name描述的就是前面提到的Image文件的路徑。如果它的值不等于空的話,那么就以它為參數,調用ImageSpace類的靜態(tài)成員函數Create創(chuàng)建一個Image空間,并且調用Heap類的成員函數AddContinuousSpace將該Image空間作為本進程的ART虛擬機堆的第一個連續(xù)空間。接下來我們繼續(xù)分析ImageSpace類的靜態(tài)成員函數Create,它的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片ImageSpace*ImageSpace::Create(conststd::string&original_image_file_name){if(OS::FileExists(original_image_file_name.c_str())){//Ifthe/systemfileexists,itshouldbeup-to-date,don'ttrytogeneratereturnspace::ImageSpace::Init(original_image_file_name,false);}//Ifthe/systemfiledidn'texist,weneedtouseonefromthedalvik-cache.//Ifthecachefileexists,trytoopen,butifitfails,regenerate.//Ifitdoesnotexist,generate.std::stringimage_file_name(GetDalvikCacheFilenameOrDie(original_image_file_name));if(OS::FileExists(image_file_name.c_str())){space::ImageSpace*image_space=space::ImageSpace::Init(image_file_name,true);if(image_space!=NULL){returnimage_space;}}CHECK(GenerateImage(image_file_name))<<"Failedtogenerateimage:"<<image_file_name;returnspace::ImageSpace::Init(image_file_name,true);}這個函數定義在文件art/runtime/gc/space/image_space.cc中。ImageSpace類的靜態(tài)成員函數Create首先是檢查參數original_image_file_name指定的Image文件是否存在。如果存在的話,就以它為參數,調用ImageSpace類的另外一個靜態(tài)成員函數Init來創(chuàng)建一個Image空間。否則的話,再調用函數GetDalvikCacheFilenameOrDie根據參數original_image_file_name構造另外一個在/data/dalvik-cache目錄下的文件路徑,然后再檢查這個文件是否存在。如果存在的話,就同樣是以它為參數,調用ImageSpace類的靜態(tài)成員函數Init來創(chuàng)建一個Image空間。否則的話,就要調用ImageSpace類的另外一個靜態(tài)成員函數GenerateImage來生成一個新的Image文件,接著再調用ImageSpace類的靜態(tài)成員函數Init來創(chuàng)建一個Image空間了。我們假設參數original_image_file_name的值等于“/system/framework/boot.art”,那么ImageSpace類的靜態(tài)成員函數Create的執(zhí)行邏輯實際上就是:1.檢查文件/system/framework/boot.art是否存在。如果存在,那么就以它為參數,創(chuàng)建一個Image空間。否則的話,執(zhí)行下一步。2.檢查文件/data/dalvik-cache/system@framework@boot.art@classes.dex是否存在。如果存在,那么就以它為參數,創(chuàng)建一個Image空間。否則的話,執(zhí)行下一步。3.調用ImageSpace類的靜態(tài)成員函數GenerateImage在/data/dalvik-cache目錄下生成一個system@framework@boot.art@classes.dex,然后再以該文件為參數,創(chuàng)建一個Image空間。接下來我們再來看看ImageSpace類的靜態(tài)成員函數GenerateImage的實現,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片staticboolGenerateImage(conststd::string&image_file_name){conststd::stringboot_class_path_string(Runtime::Current()->GetBootClassPathString());std::vector<std::string>boot_class_path;Split(boot_class_path_string,':',boot_class_path);......std::vector<std::string>arg_vector;std::stringdex2oat(GetAndroidRoot());dex2oat+=(kIsDebugBuild?"/bin/dex2oatd":"/bin/dex2oat");arg_vector.push_back(dex2oat);std::stringimage_option_string("--image=");image_option_string+=image_file_name;arg_vector.push_back(image_option_string);......for(size_ti=0;i<boot_class_path.size();i++){arg_vector.push_back(std::string("--dex-file=")+boot_class_path[i]);}std::stringoat_file_option_string("--oat-file=");oat_file_option_string+=image_file_name;oat_file_option_string.erase(oat_file_option_string.size()-3);oat_file_option_string+="oat";arg_vector.push_back(oat_file_option_string);......if(kIsTargetBuild){arg_vector.push_back("--image-classes-zip=/system/framework/framework.jar");arg_vector.push_back("--image-classes=preloaded-classes");}......//Converttheargstocharpointers.std::vector<char*>char_args;for(std::vector<std::string>::iteratorit=arg_vector.begin();it!=arg_vector.end();++it){char_args.push_back(const_cast<char*>(it->c_str()));}char_args.push_back(NULL);//forkandexecdex2oatpid_tpid=fork();if(pid==0){......execv(dex2oat.c_str(),&char_args[0]);......returnfalse;}else{......//waitfordex2oattofinishintstatus;pid_tgot_pid=TEMP_FAILURE_RETRY(waitpid(pid,&status,0));.......}returntrue;}這個函數定義在文件art/runtime/gc/space/image_space.cc中。ImageSpace類的靜態(tài)成員函數GenerateImage實際上就調用dex2oat工具在/data/dalvik-cache目錄下生成兩個文件:system@framework@boot.art@classes.dex和system@framework@boot.art@classes.oat。system@framework@boot.art@classes.dex是一個Image文件,通過--image選項傳遞給dex2oat工具,里面包含了一些需要在Zygote進程啟動時預加載的類。這些需要預加載的類由/system/framework/framework.jar文件里面的preloaded-classes文件指定。system@framework@boot.art@classes.oat是一個OAT文件,通過--oat-file選項傳遞給dex2oat工具,它是由系統(tǒng)啟動路徑中指定的jar文件生成的。每一個jar文件都通過一個--dex-file選項傳遞給dex2oat工具。這樣dex2oat工具就可以將它們所包含的classes.dex文件里面的DEX字節(jié)碼翻譯成本地機器指令。這樣,我們就得到了一個包含有多個DEX文件的OAT文件system@framework@boot.art@classes.oat。通過上面的分析,我們就清楚地看到了ART運行時所需要的OAT文件是如何產生的了。其中,由系統(tǒng)啟動類路徑指定的DEX文件生成的OAT文件稱為類型為BOOT的OAT文件,即boot.art文件。有了這個背景知識之后,接下來我們就繼續(xù)分析ART運行時是如何加載OAT文件的。ART運行時提供了一個OatFile類,通過調用它的靜態(tài)成員函數Open可以在本進程中加載OAT文件,它的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片OatFile*OatFile::Open(conststd::string&filename,conststd::string&location,byte*requested_base,boolexecutable){CHECK(!filename.empty())<<location;CheckLocation(filename);#ifdefART_USE_PORTABLE_COMPILER//IfweareusingPORTABLE,usedlopentodealwithrelocations.////WeuseourownELFloaderforQuicktodealwithlegacyappsthat//openagenerateddexfilebyname,removethefile,thenopen//anothergenerateddexfilewiththesamename.http://b/10614658if(executable){returnOpenDlopen(filename,location,requested_base);}#endif//Ifwearen'ttryingtoexecute,wejustuseourownElfFileloaderforacouplereasons:////Ontarget,dlopenmayfailwhencompilingduetoselinuxrestrictionsoninstalld.////Onhost,dlopenisexpectedtofailwhencrosscompiling,sofallbacktoOpenElfFile.//Thiswon'tworkforportableruntimeexecutionbecauseitdoesn'tprocessrelocations.UniquePtr<File>file(OS::OpenFileForReading(filename.c_str()));if(file.get()==NULL){returnNULL;}returnOpenElfFile(file.get(),location,requested_base,false,executable);}這個函數定義在文件art/runtime/oat_file.cc中。參數filename和location實際上是一樣的,指向要加載的OAT文件。參數requested_base是一個可選參數,用來描述要加載的OAT文件里面的oatdata段要加載在的位置。參數executable表示要加載的OAT是不是應用程序的主執(zhí)行文件。一般來說,一個應用程序只有一個classes.dex文件,這個classes.dex文件經過編譯后,就得到一個OAT主執(zhí)行文件。不過,應用程序也可以在運行時動態(tài)加載DEX文件。這些動態(tài)加載的DEX文件在加載的時候同樣會被翻譯成OAT再運行,它們相應打包在應用程序的classes.dex文件來說,就不屬于主執(zhí)行文件了。OatFile類的靜態(tài)成員函數Open的實現雖然只有寥寥幾行代碼,但是要理解它還得先理解宏ART_USE_PORTABLE_COMPILER的的作用。在前面一文中提到,ART運行時利用LLVM編譯框架來將DEX字節(jié)碼翻譯成本地機器指令,其中要通過一個稱為Backend的模塊來生成本地機器指令。這些生成的機器指令就保存在ELF文件格式的OAT文件的oatexec段中。ART運行時會為每一個類方法都生成一系列的本地機器指令。這些本地機器指令不是孤立存在的,因為它們可能需要其它的函數來完成自己的功能。例如,它們可能需要調用ART運行時的堆管理系統(tǒng)提供的接口來為對象分配內存空間。這樣就會涉及到一個模塊依賴性問題,就好像我們在編寫程序時,需要依賴C庫提供的接口一樣。這要求Backend為類方法生成本地機器指令時,要處理調用其它模塊提供的函數的問題。ART運行時支持兩種類型的Backend:Portable和Quick。Portable類型的Backend通過集成在LLVM編譯框架里面的一個稱為MCLinker的鏈接器來生成本地機器指令。關于MCLinker的更多知識,可以參考。簡單來說,假設我們有一個模塊A,它依賴于模塊B、C和D,那么在為模塊A生成本地機器指令時,指出它依賴于模塊B、C和D就行了。在生成的OAT文件中會記錄好這些依賴關系,這是ELF文件格式本來就支持的特性。這些OAT文件要通過系統(tǒng)的動態(tài)鏈接器提供的dlopen函數來加載。函數dlopen在加載OAT文件的時候,會通過重定位技術來處理好它與其它模塊的依賴關系,使得它能夠調用其它模塊提供的接口。這個實際上就通用的編譯器、靜態(tài)連接器以及動態(tài)鏈接器合作在一起干的事情,MCLinker扮演的就是靜態(tài)鏈接器的角色。既然是通用的技術,因為就稱能產生這種OAT文件的Backend為Portable類型的。另一方面,Quick類型的Backend生成的本地機器指令用另外一種方式來處理依賴模塊之間的依賴關系。簡單來說,就是ART運行時會在每一個線程的TLS(線程本地區(qū)域)提供一個函數表。有了這個函數表之后,Quick類型的Backend生成的本地機器指令就可以通過它來調用其它模塊的函數。也就是說,Quick類型的Backend生成的本地機器指令要依賴于ART運行時提供的函數表。這使得Quick類型的Backend生成的OAT文件在加載時不需要再處理模式之間的依賴關系。再通俗一點說的就是Quick類型的Backend生成的OAT文件在加載時不需要重定位,因此就不需要通過系統(tǒng)的動態(tài)鏈接器提供的dlopen函數來加載。由于省去重定位這個操作,Quick類型的Backend生成的OAT文件在加載時就會更快,這也是稱為Quick的緣由。關于ART運行時類型為Portable和Quick兩種類型的Backend,我們就暫時講解到這里,后面分析ART運行時執(zhí)行類方法的時候,我們再詳細分析?,F在我們需要知道的就是,如果在編譯ART運行時時,定義了宏ART_USE_PORTABLE_COMPILER,那么就表示要使用Portable類型的Backend來生成OAT文件,否則就使用Quick類型的Backend來生成OAT文件。默認情況下,使用的是Quick類型的Backend。接下就可以很好地理解OatFile類的靜態(tài)成員函數Open的實現了:1.如果編譯時指定了ART_USE_PORTABLE_COMPILER宏,并且參數executable為true,那么就通過OatFile類的靜態(tài)成員函數OpenDlopen來加載指定的OAT文件。OatFile類的靜態(tài)成員函數OpenDlopen直接通過動態(tài)鏈接器提供的dlopen函數來加載OAT文件。2.其余情況下,通過OatFile類的靜態(tài)成員函數OpenElfFile來手動加載指定的OAT文件。這種方式是按照ELF文件格式來解析要加載的OAT文件的,并且根據解析獲得的信息將OAT里面相應的段加載到內存中來。接下來我們就分別看看OatFile類的靜態(tài)成員函數OpenDlopen和OpenElfFile的實現,以便可以對OAT文件有更清楚的認識。OatFile類的靜態(tài)成員函數OpenDlopen的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片OatFile*OatFile::OpenDlopen(conststd::string&elf_filename,conststd::string&location,byte*requested_base){UniquePtr<OatFile>oat_file(newOatFile(location));boolsuccess=oat_file->Dlopen(elf_filename,requested_base);if(!success){returnNULL;}returnoat_file.release();}這個函數定義在文件art/runtime/oat_file.cc中。OatFile類的靜態(tài)成員函數OpenDlopen首先是創(chuàng)建一個OatFile對象,接著再調用該OatFile對象的成員函數Dlopen加載參數elf_filename指定的OAT文件。OatFile類的成員函數Dlopen的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片boolOatFile::Dlopen(conststd::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){......returnfalse;}end_=reinterpret_cast<byte*>(dlsym(dlopen_handle_,"oatlastword"));......//Readjusttobenon-inclusiveupperbound.end_+=sizeof(uint32_t);returnSetup();}這個函數定義在文件art/runtime/oat_file.cc中。OatFile類的成員函數Dlopen首先是通過動態(tài)鏈接器提供的dlopen函數將參數elf_filename指定的OAT文件加載到內存中來,接著同樣是通過動態(tài)鏈接器提供的dlsym函數從加載進來的OAT文件獲得兩個導出符號oatdata和oatlastword的地址,分別保存在當前正在處理的OatFile對象的成員變量begin_和end_中。根據圖1所示,符號oatdata的地址即為OAT文件里面的oatdata段加載到內存中的開始地址,而符號oatlastword的地址即為OAT文件里面的oatexec加載到內存中的結束地址。符號oatlastword本身也是屬于oatexec段的,它自己占用了一個地址,也就是sizeof(uint32_t)個字節(jié),于是將前面得到的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類的靜態(tài)成員函數OpenElfFile之后,再來看OatFile類的成員函數Setup的實現。OatFile類的靜態(tài)成員函數OpenElfFile的實現如下所示:[cpp]viewplaincopyOatFile*OatFile::OpenElfFile(File*file,conststd::string&location,byte*requested_base,boolwritable,boolexecutable){UniquePtr<OatFile>oat_file(newOatFile(location));boolsuccess=oat_file->ElfFileOpen(file,requested_base,writable,executable);if(!success){returnNULL;}returnoat_file.release();}這個函數定義在文件art/runtime/oat_file.cc中。OatFile類的靜態(tài)成員函數OpenElfFile創(chuàng)建了一個OatFile對象后,就調用它的成員函數ElfFileOpen來執(zhí)行加載OAT文件的工作,它的實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片boolOatFile::ElfFileOpen(File*file,byte*requested_base,boolwritable,boolexecutable){elf_file_.reset(ElfFile::Oen(file,writable,true));......boolloaded=elf_file_->Load(executable);......begin_=elf_file_->FindDynamicSymbolAddress("oatdata");......if(requested_base!=NULL&&begin_!=requested_base){......returnfalse;}end_=elf_file_->FindDynamicSymbolAddress("oatlastword");......//Readjusttobenon-inclusiveupperbound.end_+=sizeof(uint32_t);returnSetup();}這個函數定義在文件art/runtime/oat_file.cc中。OatFile類的靜態(tài)成員函數OpenElfFile的實現與前面分析的成員函數Dlopen是很類似的,唯一不同的是前者通過ElfFile類來手動加載參數file指定的OAT文件,實際上就是按照ELF文件格式來解析參數file指定的OAT文件,并且將文件里面的oatdata段和oatexec段加載到內存中來。我們可以將ElfFile類看作是ART運行時自己實現的OAT文件動態(tài)鏈接器。一旦參數file指定的OAT文件指定的文件加載完成之后,我們同樣是通過兩個導出符號oatdata和oatlastword來獲得oatdata段和oatexec段的起止位置。同樣,如果參數requested_base的值不等于0,那么就要求oatdata段必須要加載到requested_base指定的位置去。將參數file指定的OAT文件加載到內存之后,OatFile類的靜態(tài)成員函數OpenElfFile最后也是調用OatFile類的成員函數Setup來解析其中的oatdata段。OatFile類的成員函數Setup定義在文件art/runtime/oat_file.cc中,我們分三部分來閱讀,以便可以更好地理解OAT文件的格式。OatFile類的成員函數Setup的第一部分實現如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片boolOatFile::Setup(){if(!GetOatHeader().IsValid()){LOG(WARNING)<<"Invalidoatmagicfor"<<GetLocation();returnfalse;}constbyte*oat=Begin();oat+=sizeof(OatHeader);if(oat>End()){LOG(ERROR)<<"Inoatfile"<<GetLocation()<<"foundtruncatedOatHeader";returnfalse;}我們先來看OatFile類的三個成員函數GetOatHeader、Begin和End的實現,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片constOatHeader&OatFile::GetOatHeader()const{return*reinterpret_cast<constOatHeader*>(Begin());}constbyte*OatFile::Begin()const{CHECK(begin_!=NULL);returnbegin_;}constbyte*OatFile::End()const{CHECK(end_!=NULL);returnend_;}這三個函數主要是涉及到了OatFile類的兩個成員變量begin_和end_,它們分別是OAT文件里面的oatdata段開始地址和oatexec段的結束地址。通過OatFile類的成員函數GetOatHeader可以清楚地看到,OAT文件里面的oatdata段的開始儲存著一個OAT頭,這個OAT頭通過類OatHeader描述,定義在文件art/runtime/oat.h中,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片classPACKED(4)OatHeader{public:......private:uint8_tmagic_[4];uint8_tversion_[4];uint32_tadler32_checksum_;InstructionSetinstruction_set_;uint32_tdex_file_count_;uint32_texecutable_offset_;uint32_tinterpreter_to_interpreter_bridge_offset_;uint32_tinterpreter_to_compiled_code_bridge_offset_;uint32_tjni_dlsym_lookup_offset_;uint32_tportable_resolution_trampoline_offset_;uint32_tportable_to_interpreter_bridge_offset_;uint32_tquick_resolution_trampoline_offset_;uint32_tquick_to_interpreter_bridge_offset_;uint32_timage_file_location_oat_checksum_;uint32_timage_file_location_oat_data_begin_;uint32_timage_file_location_size_;uint8_timage_file_location_data_[0];//notevariablewidthdataatend......};類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選項指定所有類的方法都是解釋執(zhí)行的,這與傳統(tǒng)的虛擬機使用解釋器來執(zhí)行類方法差不多。同時,有些類方法可能沒有被翻譯成本地機器指令,這時候也要求對它們進行解釋執(zhí)行。這意味著解釋執(zhí)行的類方法在執(zhí)行的過程中,可能會調用到另外一個也是解釋執(zhí)行的類方法,也可能調用到另外一個按本地機器指令執(zhí)行的類方法中。OAT文件在內部提供有兩段trampoline代碼,分別用來從解釋器調用另外一個也是通過解釋器來執(zhí)行的類方法和從解釋器調用另外一個按照本地機器執(zhí)行的類方法。這兩段trampoline代碼的偏移位置就保存在成員變量interpreter_to_interpreter_bridge_offset_和interpreter_to_compiled_code_bridge_offset_。jni_dlsym_lookup_offset_:類方法在執(zhí)行的過程中,如果要調用另外一個方法是一個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_的作用剛好相反,用來在按照本地機器指令執(zhí)行的類方法中調用解釋執(zhí)行的類方法的兩段trampoline代碼。其中,portable_to_interpreter_bridge_offset_用于Portable類型的Backend生成的本地機器指令,而quick_to_interpreter_bridge_offset_用于Quick類型的Backend生成的本地機器指令。由于每一個應用程序都會依賴于boot.art文件,因此為了節(jié)省由打包在應用程序里面的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_:用來創(chuàng)建Image空間的文件的路徑的在內存中的地址。image_file_location_size_:用來創(chuàng)建Image空間的文件的路徑的大小。image_file_location_oat_data_begin_:用來創(chuàng)建Image空間的OAT文件的oatdata段在內存的位置。image_file_location_oat_checksum_:用來創(chuàng)建Image空間的OAT文件的檢驗和。上述四個成員變量記錄了一個OAT文件所依賴的用來創(chuàng)建Image空間文件以及創(chuàng)建這個Image空間文件所使用的OAT文件的相關信息。通過OatFile類的成員函數Setup的第一部分代碼的分析,我們就知道了,OAT文件的oatdata段在最開始保存著一個OAT頭,如圖2所示:我們接著再看OatFile類的成員函數Setup的第二部分代碼:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片oat+=GetOatHeader().GetImageFileLocationSize();if(oat>End()){LOG(ERROR)<<"Inoatfile"<<GetLocation()<<"foundtruncatedimagefilelocation:"<<reinterpret_cast<constvoid*>(Begin())<<"+"<<sizeof(OatHeader)<<"+"<<GetOatHeader().GetImageFileLocationSize()<<"<="<<reinterpret_cast<constvoid*>(End());returnfalse;}調用Oat

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論