Android运行时ART加载OAT文件的过程分析_第1页
Android运行时ART加载OAT文件的过程分析_第2页
Android运行时ART加载OAT文件的过程分析_第3页
Android运行时ART加载OAT文件的过程分析_第4页
Android运行时ART加载OAT文件的过程分析_第5页
已阅读5页,还剩21页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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字节码翻译成本地机器指令。这相当于是编写一个输入文件为DEX、输出文件为OAT的编译器。这个编译器是基于LLVM编译框架开发的。编译器的工作原理比较高大上,所幸的是它不会影响到我们接下来的分析,因此我们就略过DEX字节码翻译成本地机器指令的过程,假设它很愉快地完成了。APK安装过程中生成的OAT文件的输入只有一个DEX文件,也就是来自于打包在要安装的APK文件里面的classes.dex文件。实际上,一个OAT文件是可以由若干个DEX生成的。这意味着在生成的OAT文件的oatdata段中,包含有多个DEX文件。那么,在什么情况下,会生成包含多个DEX文件的OAT文件呢?从前面一文可以知道,当我们选择了ART运行时时,Zygote进程在启动的过程中,会调用libart.so里面的函数JNI_CreateJavaVM来创建一个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类的静态成员函数Create。Runtime类的静态成员函数Create负责在进程中创建一个ART虚拟机。创建成功后,就调用Runtime类的另外一个静态成员函数Start启动该ART虚拟机。注意,这个创建ART虚拟的动作只会在Zygote进程中执行,SystemServer系统进程以及Android应用程序进程的ART虚拟机都是直接从Zygote进程fork出来共享的。这与Dalvik虚拟机的创建方式是完全一样的。接下来我们就重点分析Runtime类的静态成员函数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类的静态成员变量,它指向进程中的一个Runtime单例。这个Runtime单例描述的就是当前进程的ART虚拟机实例。函数首先判断当前进程是否已经创建有一个ART虚拟机实例了。如果有的话,函数就立即返回。否则的话,就创建一个ART虚拟机实例,并且保存在Runtime类的静态成员变量instance_中,最后调用Runtime类的成员函数Init对该新创建的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类的静态成员函数Create对ART虚拟机的启动参数raw_options进行解析。解析后得到的参数保存在一个ParsedOptions对象中,接下来就根据这些参数一个ART虚拟机堆。ART虚拟机堆使用一个Heap对象来描述。创建好ART虚拟机堆后,Runtime类的成员函数Init接着又创建了一个JavaVMExt实例。这个JavaVMExt实例最终是要返回给调用者的,使得调用者可以通过该JavaVMExt实例来和ART虚拟机交互。再接下来,Runtime类的成员函数Init通过Thread类的成员函数Attach将当前线程作为ART虚拟机的主线程,使得当前线程可以调用ART虚拟机提供的JNI接口。Runtime类的成员函数GetHeap返回的便是当前ART虚拟机的堆,也就是前面创建的ART虚拟机堆。通过调用Heap类的成员函数GetContinuousSpaces可以获得堆里面的连续空间列表。如果这个列表的第一个连续空间是一个Image空间,那么就调用ClassLinker类的静态成员函数CreateFromImage来创建一个ClassLinker对象。否则的话,上述ClassLinker对象就要通过ClassLinker类的另外一个静态成员函数CreateFromCompiler来创建。创建出来的ClassLinker对象是后面ART虚拟机加载加载Java类时要用到的。后面我们分析ART虚拟机的垃圾收集机制时会看到,ART虚拟机的堆包含有三个连续空间和一个不连续空间。三个连续空间分别用来分配不同的对象。当第一个连续空间不是Image空间时,就表明当前进程不是Zygote进程,而是安装应用程序时启动的一个dex2oat进程。安装应用程序时启动的dex2oat进程也会在内部创建一个ART虚拟机,不过这个ART虚拟机是用来将DEX字节码编译成本地机器指令的,而Zygote进程创建的ART虚拟机是用来运行应用程序的。接下来我们主要分析ParsedOptions类的静态成员函数Create和ART虚拟机堆Heap的构造函数,以便可以了解ART虚拟机的启动参数解析过程和ART虚拟机的堆创建过程。ParsedOptions类的静态成员函数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用来指定启动类路径。如果没有指定启动类路径,那么默认的启动类路径就通过环境变量BOOTCLASSPATH来获得。参数-Ximage用来指定ART虚拟机所使用的Image文件。这个Image是用来启动ART虚拟机的。参数compiler用来指定当前要创建的ART虚拟机是用来将DEX字节码编译成本地机器指令的。如果没有指定Image文件,并且当前创建的ART虚拟机又不是用来编译DEX字节码的,那么就将该Image文件指定为设备上的/system/framework/boot.art文件。我们知道,system分区的文件都是在制作ROM时打包进去的。这样上述代码的逻辑就是说,如果没有指定Image文件,那么将system分区预先准备好的framework/boot.art文件作为Image文件来启动ART虚拟机。不过,/system/framework/boot.art文件可能是不存在的。在这种情况下,就需要生成一个新的Image文件。这个Image文件就是一个包含了多个DEX文件的OAT文件。接下来通过分析ART虚拟机堆的创建过程就会清楚地看到这一点。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虚拟机堆的详细创建过程我们在后面分析ART虚拟机的垃圾收集机制时再分析,这里只关注与Image文件相关的逻辑。参数original_image_file_name描述的就是前面提到的Image文件的路径。如果它的值不等于空的话,那么就以它为参数,调用ImageSpace类的静态成员函数Create创建一个Image空间,并且调用Heap类的成员函数AddContinuousSpace将该Image空间作为本进程的ART虚拟机堆的第一个连续空间。接下来我们继续分析ImageSpace类的静态成员函数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类的静态成员函数Create首先是检查参数original_image_file_name指定的Image文件是否存在。如果存在的话,就以它为参数,调用ImageSpace类的另外一个静态成员函数Init来创建一个Image空间。否则的话,再调用函数GetDalvikCacheFilenameOrDie根据参数original_image_file_name构造另外一个在/data/dalvik-cache目录下的文件路径,然后再检查这个文件是否存在。如果存在的话,就同样是以它为参数,调用ImageSpace类的静态成员函数Init来创建一个Image空间。否则的话,就要调用ImageSpace类的另外一个静态成员函数GenerateImage来生成一个新的Image文件,接着再调用ImageSpace类的静态成员函数Init来创建一个Image空间了。我们假设参数original_image_file_name的值等于“/system/framework/boot.art”,那么ImageSpace类的静态成员函数Create的执行逻辑实际上就是:1.检查文件/system/framework/boot.art是否存在。如果存在,那么就以它为参数,创建一个Image空间。否则的话,执行下一步。2.检查文件/data/dalvik-cache/system@framework@boot.art@classes.dex是否存在。如果存在,那么就以它为参数,创建一个Image空间。否则的话,执行下一步。3.调用ImageSpace类的静态成员函数GenerateImage在/data/dalvik-cache目录下生成一个system@framework@boot.art@classes.dex,然后再以该文件为参数,创建一个Image空间。接下来我们再来看看ImageSpace类的静态成员函数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类的静态成员函数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工具,它是由系统启动路径中指定的jar文件生成的。每一个jar文件都通过一个--dex-file选项传递给dex2oat工具。这样dex2oat工具就可以将它们所包含的classes.dex文件里面的DEX字节码翻译成本地机器指令。这样,我们就得到了一个包含有多个DEX文件的OAT文件system@framework@boot.art@classes.oat。通过上面的分析,我们就清楚地看到了ART运行时所需要的OAT文件是如何产生的了。其中,由系统启动类路径指定的DEX文件生成的OAT文件称为类型为BOOT的OAT文件,即boot.art文件。有了这个背景知识之后,接下来我们就继续分析ART运行时是如何加载OAT文件的。ART运行时提供了一个OatFile类,通过调用它的静态成员函数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是不是应用程序的主执行文件。一般来说,一个应用程序只有一个classes.dex文件,这个classes.dex文件经过编译后,就得到一个OAT主执行文件。不过,应用程序也可以在运行时动态加载DEX文件。这些动态加载的DEX文件在加载的时候同样会被翻译成OAT再运行,它们相应打包在应用程序的classes.dex文件来说,就不属于主执行文件了。OatFile类的静态成员函数Open的实现虽然只有寥寥几行代码,但是要理解它还得先理解宏ART_USE_PORTABLE_COMPILER的的作用。在前面一文中提到,ART运行时利用LLVM编译框架来将DEX字节码翻译成本地机器指令,其中要通过一个称为Backend的模块来生成本地机器指令。这些生成的机器指令就保存在ELF文件格式的OAT文件的oatexec段中。ART运行时会为每一个类方法都生成一系列的本地机器指令。这些本地机器指令不是孤立存在的,因为它们可能需要其它的函数来完成自己的功能。例如,它们可能需要调用ART运行时的堆管理系统提供的接口来为对象分配内存空间。这样就会涉及到一个模块依赖性问题,就好像我们在编写程序时,需要依赖C库提供的接口一样。这要求Backend为类方法生成本地机器指令时,要处理调用其它模块提供的函数的问题。ART运行时支持两种类型的Backend:Portable和Quick。Portable类型的Backend通过集成在LLVM编译框架里面的一个称为MCLinker的链接器来生成本地机器指令。关于MCLinker的更多知识,可以参考。简单来说,假设我们有一个模块A,它依赖于模块B、C和D,那么在为模块A生成本地机器指令时,指出它依赖于模块B、C和D就行了。在生成的OAT文件中会记录好这些依赖关系,这是ELF文件格式本来就支持的特性。这些OAT文件要通过系统的动态链接器提供的dlopen函数来加载。函数dlopen在加载OAT文件的时候,会通过重定位技术来处理好它与其它模块的依赖关系,使得它能够调用其它模块提供的接口。这个实际上就通用的编译器、静态连接器以及动态链接器合作在一起干的事情,MCLinker扮演的就是静态链接器的角色。既然是通用的技术,因为就称能产生这种OAT文件的Backend为Portable类型的。另一方面,Quick类型的Backend生成的本地机器指令用另外一种方式来处理依赖模块之间的依赖关系。简单来说,就是ART运行时会在每一个线程的TLS(线程本地区域)提供一个函数表。有了这个函数表之后,Quick类型的Backend生成的本地机器指令就可以通过它来调用其它模块的函数。也就是说,Quick类型的Backend生成的本地机器指令要依赖于ART运行时提供的函数表。这使得Quick类型的Backend生成的OAT文件在加载时不需要再处理模式之间的依赖关系。再通俗一点说的就是Quick类型的Backend生成的OAT文件在加载时不需要重定位,因此就不需要通过系统的动态链接器提供的dlopen函数来加载。由于省去重定位这个操作,Quick类型的Backend生成的OAT文件在加载时就会更快,这也是称为Quick的缘由。关于ART运行时类型为Portable和Quick两种类型的Backend,我们就暂时讲解到这里,后面分析ART运行时执行类方法的时候,我们再详细分析。现在我们需要知道的就是,如果在编译ART运行时时,定义了宏ART_USE_PORTABLE_COMPILER,那么就表示要使用Portable类型的Backend来生成OAT文件,否则就使用Quick类型的Backend来生成OAT文件。默认情况下,使用的是Quick类型的Backend。接下就可以很好地理解OatFile类的静态成员函数Open的实现了:1.如果编译时指定了ART_USE_PORTABLE_COMPILER宏,并且参数executable为true,那么就通过OatFile类的静态成员函数OpenDlopen来加载指定的OAT文件。OatFile类的静态成员函数OpenDlopen直接通过动态链接器提供的dlopen函数来加载OAT文件。2.其余情况下,通过OatFile类的静态成员函数OpenElfFile来手动加载指定的OAT文件。这种方式是按照ELF文件格式来解析要加载的OAT文件的,并且根据解析获得的信息将OAT里面相应的段加载到内存中来。接下来我们就分别看看OatFile类的静态成员函数OpenDlopen和OpenElfFile的实现,以便可以对OAT文件有更清楚的认识。OatFile类的静态成员函数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类的静态成员函数OpenDlopen首先是创建一个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首先是通过动态链接器提供的dlopen函数将参数elf_filename指定的OAT文件加载到内存中来,接着同样是通过动态链接器提供的dlsym函数从加载进来的OAT文件获得两个导出符号oatdata和oatlastword的地址,分别保存在当前正在处理的OatFile对象的成员变量begin_和end_中。根据图1所示,符号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的实现。OatFile类的静态成员函数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类的静态成员函数OpenElfFile创建了一个OatFile对象后,就调用它的成员函数ElfFileOpen来执行加载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类的静态成员函数OpenElfFile的实现与前面分析的成员函数Dlopen是很类似的,唯一不同的是前者通过ElfFile类来手动加载参数file指定的OAT文件,实际上就是按照ELF文件格式来解析参数file指定的OAT文件,并且将文件里面的oatdata段和oatexec段加载到内存中来。我们可以将ElfFile类看作是ART运行时自己实现的OAT文件动态链接器。一旦参数file指定的OAT文件指定的文件加载完成之后,我们同样是通过两个导出符号oatdata和oatlastword来获得oatdata段和oatexec段的起止位置。同样,如果参数requested_base的值不等于0,那么就要求oatdata段必须要加载到requested_base指定的位置去。将参数file指定的OAT文件加载到内存之后,OatFile类的静态成员函数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选项指定所有类的方法都是解释执行的,这与传统的虚拟机使用解释器来执行类方法差不多。同时,有些类方法可能没有被翻译成本地机器指令,这时候也要求对它们进行解释执行。这意味着解释执行的类方法在执行的过程中,可能会调用到另外一个也是解释执行的类方法,也可能调用到另外一个按本地机器指令执行的类方法中。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头,如图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. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论