Android6.0中oat文件的加载过程

来源:互联网 发布:mac安装ie浏览器 编辑:程序博客网 时间:2024/05/18 05:05

主要参考:http://blog.csdn.net/luoshengyang/article/details/39307813 罗升阳老师的Android运行时ART加载OAT文件的过程分析 。将代码换成了Android6.0部分,并且对其中某些内容进行了修改,比如oat文件的内容等。

在分析OAT文件的加载过程之前,我们需要简单介绍一下OAT是如何产生的。如前面Android ART运行时无缝替换Dalvik虚拟机的过程分析一文所示,APK在安装的过程中,会通过dex2oat工具生成一个OAT文件:

 747 static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name, 748     const char* output_file_name, int swap_fd, const char *pkgname, const char *instruction_set, 749     bool vm_safe_mode, bool debuggable, bool post_bootcomplete) 750 {     ... 813     static const char* DEX2OAT_BIN = "/system/bin/dex2oat"; 814  815     static const char* RUNTIME_ARG = "--runtime-arg";    ... 834     sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd); 835     sprintf(zip_location_arg, "--zip-location=%s", input_file_name); 836     sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd); 837     sprintf(oat_location_arg, "--oat-location=%s", output_file_name); 838     sprintf(instruction_set_arg, "--instruction-set=%s", instruction_set); 839     sprintf(instruction_set_variant_arg, "--instruction-set-variant=%s", dex2oat_isa_variant); 840     sprintf(instruction_set_features_arg, "--instruction-set-features=%s", dex2oat_isa_features);    ... 958  959     execv(DEX2OAT_BIN, (char * const *)argv); 960     ALOGE("execv(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno)); 961 }

这个函数定义在文件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文件呢?
从前面Android ART运行时无缝替换Dalvik虚拟机的过程分析一文可以知道,当我们选择了ART运行时时,Zygote进程在启动的过程中,会调用libart.so里面的函数JNI_CreateJavaVM来创建一个ART虚拟机。函数JNI_CreateJavaVM的实现如下所示:

789 // JNI Invocation interface.790 791 extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {792   ATRACE_BEGIN(__FUNCTION__);793   const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);794   if (IsBadJniVersion(args->version)) {795     LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;796     ATRACE_END();797     return JNI_EVERSION;798   }799   RuntimeOptions options;800   for (int i = 0; i < args->nOptions; ++i) {801     JavaVMOption* option = &args->options[i];802     options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));803   }804   bool ignore_unrecognized = args->ignoreUnrecognized;805   if (!Runtime::Create(options, ignore_unrecognized)) {806     ATRACE_END();807     return JNI_ERR;808   }809   Runtime* runtime = Runtime::Current();810   bool started = runtime->Start();811   if (!started) {812     delete Thread::Current()->GetJniEnv();813     delete runtime->GetJavaVM();814     LOG(WARNING) << "CreateJavaVM failed";815     ATRACE_END();816     return JNI_ERR;817   }818   *p_env = Thread::Current()->GetJniEnv();819   *p_vm = runtime->GetJavaVM();820   ATRACE_END();821   return JNI_OK;822 }

这个函数定义在文件art/runtime/java_vm_ext.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,它的实现如下所示:

 410 bool Runtime::Create(const RuntimeOptions& options, bool ignore_unrecognized) { 411   // TODO: acquire a static mutex on Runtime to avoid racing. 412   if (Runtime::instance_ != nullptr) { 413     return false; 414   } 415   InitLogging(nullptr);  // Calls Locks::Init() as a side effect. 416   instance_ = new Runtime; 417   if (!instance_->Init(options, ignore_unrecognized)) { 418     // TODO: Currently deleting the instance will abort the runtime on destruction. Now This will 419     // leak memory, instead. Fix the destructor. b/19100793. 420     // delete instance_; 421     instance_ = nullptr; 422     return false; 423   } 424   return true; 425 }

这个函数定义在文件art/runtime/runtime.cc中。
instance_是Runtime类的静态成员变量,它指向进程中的一个Runtime单例。这个Runtime单例描述的就是当前进程的ART虚拟机实例。
函数首先判断当前进程是否已经创建有一个ART虚拟机实例了。如果有的话,函数就立即返回。否则的话,就创建一个ART虚拟机实例,并且保存在Runtime类的静态成员变量instance_中,最后调用Runtime类的成员函数Init对该新创建的ART虚拟机进行初始化。
Runtime类的成员函数Init的实现如下所示:

 782 bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {    ... 788   using Opt = RuntimeArgumentMap; 789   RuntimeArgumentMap runtime_options; 790   std::unique_ptr<ParsedOptions> parsed_options( 791       ParsedOptions::Create(raw_options, ignore_unrecognized, &runtime_options));     ... 849   XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); 850   ATRACE_BEGIN("CreateHeap"); 851   heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), 852                        runtime_options.GetOrDefault(Opt::HeapGrowthLimit), 853                        runtime_options.GetOrDefault(Opt::HeapMinFree), 854                        runtime_options.GetOrDefault(Opt::HeapMaxFree), 855                        runtime_options.GetOrDefault(Opt::HeapTargetUtilization), 856                        runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier), 857                        runtime_options.GetOrDefault(Opt::MemoryMaximumSize), 858                        runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity), 859                        runtime_options.GetOrDefault(Opt::Image), 860                        runtime_options.GetOrDefault(Opt::ImageInstructionSet), 861                        xgc_option.collector_type_, 862                        runtime_options.GetOrDefault(Opt::BackgroundGc), 863                        runtime_options.GetOrDefault(Opt::LargeObjectSpace), 864                        runtime_options.GetOrDefault(Opt::LargeObjectThreshold), 865                        runtime_options.GetOrDefault(Opt::ParallelGCThreads), 866                        runtime_options.GetOrDefault(Opt::ConcGCThreads), 867                        runtime_options.Exists(Opt::LowMemoryMode), 868                        runtime_options.GetOrDefault(Opt::LongPauseLogThreshold), 869                        runtime_options.GetOrDefault(Opt::LongGCLogThreshold), 870                        runtime_options.Exists(Opt::IgnoreMaxFootprint), 871                        runtime_options.GetOrDefault(Opt::UseTLAB), 872                        xgc_option.verify_pre_gc_heap_, 873                        xgc_option.verify_pre_sweeping_heap_, 874                        xgc_option.verify_post_gc_heap_, 875                        xgc_option.verify_pre_gc_rosalloc_, 876                        xgc_option.verify_pre_sweeping_rosalloc_, 877                        xgc_option.verify_post_gc_rosalloc_, 878                        xgc_option.gcstress_, 879                        runtime_options.GetOrDefault(Opt::EnableHSpaceCompactForOOM), 880                        runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));    ... 967   java_vm_ = new JavaVMExt(this, runtime_options); 968  969   Thread::Startup(); 970  971   // ClassLinker needs an attached thread, but we can't fully attach a thread without creating 972   // objects. We can't supply a thread group yet; it will be fixed later. Since we are the main 973   // thread, we do not get a java peer. 974   Thread* self = Thread::Attach("main", false, nullptr, false);    ... 984   CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U); 985   class_linker_ = new ClassLinker(intern_table_); 986   if (GetHeap()->HasImageSpace()) { 987     ATRACE_BEGIN("InitFromImage"); 988     class_linker_->InitFromImage();    ...1021     class_linker_->InitWithoutImage(std::move(boot_class_path));    ...1124   return true;1125 }

这个函数定义在文件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可以获得堆里面的连续空间列表。创建一个ClassLinker对象class_linker_,如果这个列表的第一个连续空间是一个Image空间,那么就调用InitFromImage函数。否则的话,调用InitWithoutImage。创建出来的ClassLinker对象是后面ART虚拟机加载加载Java类时要用到的。
后面我们分析ART虚拟机的垃圾收集机制时会看到,ART虚拟机的堆包含有三个连续空间和一个不连续空间。三个连续空间分别用来分配不同的对象。当第一个连续空间不是Image空间时,就表明当前进程不是Zygote进程,而是安装应用程序时启动的一个dex2oat进程。安装应用程序时启动的dex2oat进程也会在内部创建一个ART虚拟机,不过这个ART虚拟机是用来将DEX字节码编译成本地机器指令的,而Zygote进程创建的ART虚拟机是用来运行应用程序的。
接下来我们主要分析ParsedOptions类的静态成员函数Create和ART虚拟机堆Heap的构造函数,以便可以了解ART虚拟机的启动参数解析过程和ART虚拟机的堆创建过程。
ParsedOptions类的静态成员函数Create的实现如下所示:

 44 ParsedOptions* ParsedOptions::Create(const RuntimeOptions& options, bool ignore_unrecognized, 45                                      RuntimeArgumentMap* runtime_options) { 46   CHECK(runtime_options != nullptr); 47  48   std::unique_ptr<ParsedOptions> parsed(new ParsedOptions()); 49   if (parsed->Parse(options, ignore_unrecognized, runtime_options)) { 50     return parsed.release(); 51   } 52   return nullptr; 53 }

这个函数定义在文件~/android-6.0.1_r62/art/runtime/runtime.cc中。
具体的parse过程在静态成员函数Parse内,而具体的启动参数的定义在MakeParser函数中。
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虚拟机堆的创建过程就会清楚地看到这一点。
…中间内容略去

通过上面的分析,我们就清楚地看到了ART运行时所需要的OAT文件是如何产生的了。其中,由系统启动类路径指定的DEX文件生成的OAT文件称为类型为BOOT的OAT文件,即boot.art文件。有了这个背景知识之后,接下来我们就继续分析ART运行时是如何加载OAT文件的。
ART运行时提供了一个OatFile类,通过调用它的静态成员函数Open可以在本进程中加载OAT文件,它的实现如下所示:

102 OatFile* OatFile::Open(const std::string& filename,103                        const std::string& location,104                        uint8_t* requested_base,105                        uint8_t* oat_file_begin,106                        bool executable,107                        const char* abs_dex_location,108                        std::string* error_msg) {109   CHECK(!filename.empty()) << location;110   CheckLocation(location);111   std::unique_ptr<OatFile> ret;112 113   // Use dlopen only when flagged to do so, and when it's OK to load things executable.114   // TODO: Also try when not executable? The issue here could be re-mapping as writable (as115   //       !executable is a sign that we may want to patch), which may not be allowed for116   //       various reasons.117   if (kUseDlopen && (kIsTargetBuild || kUseDlopenOnHost) && executable) {118     // Try to use dlopen. This may fail for various reasons, outlined below. We try dlopen, as119     // this will register the oat file with the linker and allows libunwind to find our info.120     ret.reset(OpenDlopen(filename, location, requested_base, abs_dex_location, error_msg));121     if (ret.get() != nullptr) {122       return ret.release();123     } 124     if (kPrintDlOpenErrorMessage) {125       LOG(ERROR) << "Failed to dlopen: " << *error_msg;126     } 127   } 128   129   // If we aren't trying to execute, we just use our own ElfFile loader for a couple reasons:130   //131   // On target, dlopen may fail when compiling due to selinux restrictions on installd.132   //133   // We use our own ELF loader for Quick to deal with legacy apps that134   // open a generated dex file by name, remove the file, then open135   // another generated dex file with the same name. http://b/10614658136   //137   // On host, dlopen is expected to fail when cross compiling, so fall back to OpenElfFile.138   //139   //140   // Another independent reason is the absolute placement of boot.oat. dlopen on the host usually141   // does honor the virtual address encoded in the ELF file only for ET_EXEC files, not ET_DYN.142   std::unique_ptr<File> file(OS::OpenFileForReading(filename.c_str()));143   if (file == nullptr) {144     *error_msg = StringPrintf("Failed to open oat filename for reading: %s", strerror(errno));145     return nullptr;146   } 147   ret.reset(OpenElfFile(file.get(), location, requested_base, oat_file_begin, false, executable,148                         abs_dex_location, error_msg));149                         150   // It would be nice to unlink here. But we might have opened the file created by the151   // ScopedLock, which we better not delete to avoid races. TODO: Investigate how to fix the API152   // to allow removal when we know the ELF must be borked.153   return ret.release();154 } 155 156 OatFile* OatFile::OpenWritable(File* file, const std::string& location,157                                const char* abs_dex_location,158                                std::string* error_msg) {159   CheckLocation(location);160   return OpenElfFile(file, location, nullptr, nullptr, true, false, abs_dex_location, error_msg);161 }

这个函数定义在文件art/runtime/oat_file.cc中。
参数filename和location实际上是一样的,指向要加载的OAT文件。参数requested_base是一个可选参数,用来描述要加载的OAT文件里面的oatdata段要加载在的位置。参数executable表示要加载的OAT是不是应用程序的主执行文件。一般来说,一个应用程序只有一个classes.dex文件, 这个classes.dex文件经过编译后,就得到一个OAT主执行文件。不过,应用程序也可以在运行时动态加载DEX文件。这些动态加载的DEX文件在加载的时候同样会被翻译成OAT再运行,它们相应打包在应用程序的classes.dex文件来说,就不属于主执行文件了。
ART利用optimizing compiler后端来对dex字节码进行编译生成机器指令,这些生成的机器指令就保存在ELF文件格式的OAT文件的oatexec段中。
ART运行时会为每一个类方法都生成一系列的本地机器指令。这些本地机器指令不是孤立存在的,因为它们可能需要其它的函数来完成自己的功能。例如,它们可能需要调用ART运行时的堆管理系统提供的接口来为对象分配内存空间。这样就会涉及到一个模块依赖性问题,就好像我们在编写程序时,需要依赖C库提供的接口一样。这要求Backend为类方法生成本地机器指令时,要处理调用其它模块提供的函数的问题。
ART运行时支持两种后端,optimizing和quick
Quick类型的Backend生成的本地机器指令用另外一种方式来处理依赖模块之间的依赖关系。简单来说,就是ART运行时会在每一个线程的TLS(线程本地区域)提供一个函数表。有了这个函数表之后,Quick类型的Backend生成的本地机器指令就可以通过它来调用其它模块的函数。也就是说,Quick类型的Backend生成的本地机器指令要依赖于ART运行时提供的函数表。这使得Quick类型的Backend生成的OAT文件在加载时不需要再处理模式之间的依赖关系。再通俗一点说的就是Quick类型的Backend生成的OAT文件在加载时不需要重定位,因此就不需要通过系统的动态链接器提供的dlopen函数来加载。由于省去重定位这个操作,Quick类型的Backend生成的OAT文件在加载时就会更快,这也是称为Quick的缘由。
接下就可以很好地理解OatFile类的静态成员函数Open的实现了:
1. 如果编译时kUseDlopen为true,kIsTargetBuild或者kUseDlopenOnHost为true,并且参数executable为true,那么就通过OatFile类的静态成员函数OpenDlopen来加载指定的OAT文件。OatFile类的静态成员函数OpenDlopen直接通过动态链接器提供的dlopen函数来加载OAT文件。

///home/orz/android-6.0.1_r62/art/runtime/globals.h 54 // Whether or not this is a target (vs host) build. Useful in conditionals where ART_TARGET isn't. 55 #if defined(ART_TARGET) 56 static constexpr bool kIsTargetBuild = true; 57 #else 58 static constexpr bool kIsTargetBuild = false; 59 #endif //~/android-6.0.1_r62/art/runtime/oat_file.cc  54 // Whether OatFile::Open will try DlOpen() on the host. On the host we're not linking against 55 // bionic, so cannot take advantage of the support for changed semantics (loading the same soname 56 // multiple times). However, if/when we switch the above, we likely want to switch this, too, 57 // to get test coverage of the code paths. 58 static constexpr bool kUseDlopenOnHost = true;
   2. 其余情况下,通过OatFile类的静态成员函数OpenElfFile来手动加载指定的OAT文件。这种方式是按照ELF文件格式来解析要加载的OAT文件的,并且根据解析获得的信息将OAT里面相应的段加载到内存中来。

接下来我们就分别看看OatFile类的静态成员函数OpenDlopen和OpenElfFile的实现,以便可以对OAT文件有更清楚的认识。

170 OatFile* OatFile::OpenDlopen(const std::string& elf_filename,171                              const std::string& location,172                              uint8_t* requested_base,173                              const char* abs_dex_location,174                              std::string* error_msg) {175   std::unique_ptr<OatFile> oat_file(new OatFile(location, true));176   bool success = oat_file->Dlopen(elf_filename, requested_base, abs_dex_location, error_msg);177   if (!success) {178     return nullptr;179   }180   return oat_file.release();181 }

这个函数定义在文件art/runtime/oat_file.cc中。
OatFile类的静态成员函数OpenDlopen首先是创建一个OatFile对象,接着再调用该OatFile对象的成员函数Dlopen加载参数elf_filename指定的OAT文件。
OatFile类的成员函数Dlopen的实现如下所示:

215 bool OatFile::Dlopen(const std::string& elf_filename, uint8_t* requested_base,216                      const char* abs_dex_location, std::string* error_msg) {    ...231 #ifdef HAVE_ANDROID_OS232   android_dlextinfo extinfo;233   extinfo.flags = ANDROID_DLEXT_FORCE_LOAD | ANDROID_DLEXT_FORCE_FIXED_VADDR;234   dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo);235 #else236   dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW);237 #endif    ...242   begin_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatdata"));    ...248   if (requested_base != nullptr && begin_ != requested_base) {249     PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);250     *error_msg = StringPrintf("Failed to find oatdata symbol at expected address: "251                               "oatdata=%p != expected=%p, %s. See process maps in the log.",252                               begin_, requested_base, elf_filename.c_str());253     return false;254   }255   end_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatlastword"));    ...261   // Readjust to be non-inclusive upper bound.262   end_ += sizeof(uint32_t);    ...321   return Setup(abs_dex_location, error_msg);322 #endif  // __APPLE__323 }

这个函数定义在文件art/runtime/oat_file.cc中。
使用的是安卓5.0后提供的新的动态库加载函数android_dlopen_ext函数(具体的内容参见:http://www.jcodecraeer.com/plus/view.php?aid=7796),返回dlextinfo,而非android的,则是调用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类的静态成员函数OpenElfFile的实现如下所示:

183 OatFile* OatFile::OpenElfFile(File* file,184                               const std::string& location,185                               uint8_t* requested_base,186                               uint8_t* oat_file_begin,187                               bool writable,188                               bool executable,189                               const char* abs_dex_location,190                               std::string* error_msg) {191   std::unique_ptr<OatFile> oat_file(new OatFile(location, executable));192   bool success = oat_file->ElfFileOpen(file, requested_base, oat_file_begin, writable, executable,193                                        abs_dex_location, error_msg);194   if (!success) {195     CHECK(!error_msg->empty());196     return nullptr;197   }198   return oat_file.release();199 }

这个函数定义在文件art/runtime/oat_file.cc中。
OatFile类的静态成员函数OpenElfFile创建了一个OatFile对象后,就调用它的成员函数ElfFileOpen来执行加载OAT文件的工作,它的实现如下所示:

325 bool OatFile::ElfFileOpen(File* file, uint8_t* requested_base, uint8_t* oat_file_begin,326                           bool writable, bool executable,327                           const char* abs_dex_location,328                           std::string* error_msg) {    ...336   bool loaded = elf_file_->Load(executable, error_msg);    ...341   begin_ = elf_file_->FindDynamicSymbolAddress("oatdata");    ...346   if (requested_base != nullptr && begin_ != requested_base) {347     PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);348     *error_msg = StringPrintf("Failed to find oatdata symbol at expected address: "349                               "oatdata=%p != expected=%p. See process maps in the log.",350                               begin_, requested_base);351     return false;352   }353   end_ = elf_file_->FindDynamicSymbolAddress("oatlastword");    ...358   // Readjust to be non-inclusive upper bound.359   end_ += sizeof(uint32_t);    ...377   return Setup(abs_dex_location, error_msg);378 }

这个函数定义在文件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的第一部分实现如下所示:

380 bool OatFile::Setup(const char* abs_dex_location, std::string* error_msg) {381   if (!GetOatHeader().IsValid()) {382     std::string cause = GetOatHeader().GetValidationErrorMessage();383     *error_msg = StringPrintf("Invalid oat header for '%s': %s", GetLocation().c_str(),384                               cause.c_str());385     return false;386   }387   const uint8_t* oat = Begin();388   oat += sizeof(OatHeader);389   if (oat > End()) {390     *error_msg = StringPrintf("In oat file '%s' found truncated OatHeader", GetLocation().c_str());391     return false;392   }

我们先来看OatFile类的三个成员函数GetOatHeader、Begin和End的实现,如下所示:

506 const OatHeader& OatFile::GetOatHeader() const {507   return *reinterpret_cast<const OatHeader*>(Begin());508 }509     510 const uint8_t* OatFile::Begin() const {511   CHECK(begin_ != nullptr);512   return begin_;513 } 514     515 const uint8_t* OatFile::End() const {516   CHECK(end_ != nullptr);517   return end_;518 }

这三个函数主要是涉及到了OatFile类的两个成员变量begin_和end_,它们分别是OAT文件里面的oatdata段开始地址和oatexec段的结束地址。
通过OatFile类的成员函数GetOatHeader可以清楚地看到,OAT文件里面的oatdata段的开始储存着一个OAT头,这个OAT头通过类OatHeader描述,定义在文件art/runtime/oat.h中,如下所示:

 32 class PACKED(4) OatHeader { 33  public:    ...108  private:109   OatHeader(InstructionSet instruction_set,110             const InstructionSetFeatures* instruction_set_features,111             const std::vector<const DexFile*>* dex_files,112             uint32_t image_file_location_oat_checksum,113             uint32_t image_file_location_oat_data_begin,114             const SafeMap<std::string, std::string>* variable_data);115 116   // Returns true if the value of the given key is "true", false otherwise.117   bool IsKeyEnabled(const char* key) const;118 119   void Flatten(const SafeMap<std::string, std::string>* variable_data);120 121   uint8_t magic_[4];122   uint8_t version_[4];123   uint32_t adler32_checksum_;124 125   InstructionSet instruction_set_;126   uint32_t instruction_set_features_bitmap_;127   uint32_t dex_file_count_;128   uint32_t executable_offset_;129   uint32_t interpreter_to_interpreter_bridge_offset_;130   uint32_t interpreter_to_compiled_code_bridge_offset_;131   uint32_t jni_dlsym_lookup_offset_;132   uint32_t quick_generic_jni_trampoline_offset_;133   uint32_t quick_imt_conflict_trampoline_offset_;134   uint32_t quick_resolution_trampoline_offset_;135   uint32_t quick_to_interpreter_bridge_offset_;136 137   // The amount that the image this oat is associated with has been patched.138   int32_t image_patch_delta_;139 140   uint32_t image_file_location_oat_checksum_;141   uint32_t image_file_location_oat_data_begin_;142 143   uint32_t key_value_store_size_;144   uint8_t key_value_store_[0];  // note variable width data at end145 146   DISALLOW_COPY_AND_ASSIGN(OatHeader);147 };

类OatHeader的各个成员变量的含义如下所示:
magic: 标志OAT文件的一个魔数,等于‘oat\n’。
version: OAT文件版本号,目前的值等于‘064\0’。
adler32_checksum_: OAT头部检验和。
instruction_ set_: 本地机指令集,有四种取值,分别为 kNone(0), kArm(1), kArm64(2), kThumb2(3), kX86(4), kX86_64(5),kMips(6),kMips64(7)。
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代码来调用。
quick_generic_jni_trampoline_offset_ 和 quick_imt_conflict_trampoline_offset_
quick_resolution_trampoline_offset_ 和 quick_to_interpreter_bridge_offset_:前者用于在运行时解析还未链接的类方法的两段trampoline代码,用于Quick类型的Backend生成的本地机器指令;后者用来在按照本地机器指令执行的类方法中调用解释执行的类方法的两段trampoline代码,用于Quick类型的Backend生成的本地机器指令。
由于每一个应用程序都会依赖于boot.art文件,因此为了节省由打包在应用程序里面的classes.dex生成的OAT文件的体积,上述七个成员变量指向的trampoline代码段只存在于boot.art文件中。换句话说,在由打包在应用程序里面的classes.dex生成的OAT文件的oatdata段头部中,上述七个成员变量的值均等于0。
image_file_location_oat_data_begin_: 用来创建Image空间的OAT文件的oatdata段在内存的位置。
image_file_location_oat_checksum_: 用来创建Image空间的OAT文件的检验和。
通过OatFile类的成员函数Setup的第一部分代码的分析,我们就知道了,OAT文件的oatdata段在最开始保存着一个OAT头.

我们接着再看OatFile类的成员函数Setup的第二部分代码:

394   oat += GetOatHeader().GetKeyValueStoreSize();395   if (oat > End()) {396     *error_msg = StringPrintf("In oat file '%s' found truncated variable-size data: "397                               "%p + %zd + %ud <= %p", GetLocation().c_str(),398                               Begin(), sizeof(OatHeader), GetOatHeader().GetKeyValueStoreSize(),399                               End());400     return false;401   }

调用OatFile类的成员函数GetOatHeader获得的是正在打开的OAT文件的头部OatHeader,通过调用它的成员函数GetKeyValueStoreSize,获得的是保存的key value的存储大小key_value_store_size_。变量oat最开始的时候指向oatdata段的开始位置。读出OAT头之后,变量oat就跳过了OAT头。由于正在打开的OAT文件引用的Image空间文件路径保存在紧接着OAT头的地方。因此,将Image空间文件的路径大小增加到变量oat去后,就相当于是跳过了保存Image空间文件路径的位置。
通过OatFile类的成员函数Setup的第二部分代码的分析,我们就知道了,紧接着在OAT头后面的是KeyValue。

我们接着再看OatFile类的成员函数Setup的第三部分代码:

403   uint32_t dex_file_count = GetOatHeader().GetDexFileCount();404   oat_dex_files_storage_.reserve(dex_file_count);405   for (size_t i = 0; i < dex_file_count; i++) {406     uint32_t dex_file_location_size = *reinterpret_cast<const uint32_t*>(oat);//dexfile路径大小    ...412     oat += sizeof(dex_file_location_size);    ...419     const char* dex_file_location_data = reinterpret_cast<const char*>(oat);//dexfile文件路径420     oat += dex_file_location_size;    ...427     std::string dex_file_location = ResolveRelativeEncodedDexLocation(428         abs_dex_location,429         std::string(dex_file_location_data, dex_file_location_size));430 431     uint32_t dex_file_checksum = *reinterpret_cast<const uint32_t*>(oat);//DexFile文件检验和432     oat += sizeof(dex_file_checksum);    ...440     uint32_t dex_file_offset = *reinterpret_cast<const uint32_t*>(oat);//DEX文件内容在oatdata段的偏移    ...452     oat += sizeof(dex_file_offset);    ...460     const uint8_t* dex_file_pointer = Begin() + dex_file_offset;461     if (UNLIKELY(!DexFile::IsMagicValid(dex_file_pointer))) {    ...465       return false;466     }467     if (UNLIKELY(!DexFile::IsVersionValid(dex_file_pointer))) {    ...471       return false;472     }473     const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_pointer);474     const uint32_t* methods_offsets_pointer = reinterpret_cast<const uint32_t*>(oat);//DEX文件包含的类的本地机器指令信息偏移数组475 476     oat += (sizeof(*methods_offsets_pointer) * header->class_defs_size_);    ...495     // Add the location and canonical location (if different) to the oat_dex_files_ table.496     StringPiece key(oat_dex_file->GetDexFileLocation());497     oat_dex_files_.Put(key, oat_dex_file);498     if (canonical_location != dex_file_location) {499       StringPiece canonical_key(oat_dex_file->GetCanonicalDexFileLocation());500       oat_dex_files_.Put(canonical_key, oat_dex_file);501     }502   }503   return true;504 }

这部分代码用来获得包含在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对象中,以便以后可以直接访问。

为了进一步理解包含在oatdata段的DEX文件描述信息,我们继续看OatDexFile类的构造函数的实现,如下所示:

600 OatFile::OatDexFile::OatDexFile(const OatFile* oat_file,601                                 const std::string& dex_file_location,602                                 const std::string& canonical_dex_file_location,603                                 uint32_t dex_file_location_checksum,604                                 const uint8_t* dex_file_pointer,605                                 const uint32_t* oat_class_offsets_pointer)606     : oat_file_(oat_file),607       dex_file_location_(dex_file_location),608       canonical_dex_file_location_(canonical_dex_file_location),609       dex_file_location_checksum_(dex_file_location_checksum),610       dex_file_pointer_(dex_file_pointer),611       oat_class_offsets_pointer_(oat_class_offsets_pointer) {}

这个函数定义在文件art/runtime/oat_file.cc中。
OatDexFile类的构造函数的实现很简单,它将我们在上面得到的DEX文件描述信息保存在相应的成员变量中。通过这些信息,我们就可以获得包含在该DEX文件里面的类的所有方法的本地机器指令信息
例如,通过调用OatDexFile类的成员函数GetOatClass可以获得指定类的所有方法的本地机器指令信息:

628 OatFile::OatClass OatFile::OatDexFile::GetOatClass(uint16_t class_def_index) const {629   uint32_t oat_class_offset = GetOatClassOffset(class_def_index);630 631   const uint8_t* oat_class_pointer = oat_file_->Begin() + oat_class_offset;632   CHECK_LT(oat_class_pointer, oat_file_->End()) << oat_file_->GetLocation();633 634   const uint8_t* status_pointer = oat_class_pointer;635   CHECK_LT(status_pointer, oat_file_->End()) << oat_file_->GetLocation();636   mirror::Class::Status status =637       static_cast<mirror::Class::Status>(*reinterpret_cast<const int16_t*>(status_pointer));638   CHECK_LT(status, mirror::Class::kStatusMax);639 640   const uint8_t* type_pointer = status_pointer + sizeof(uint16_t);641   CHECK_LT(type_pointer, oat_file_->End()) << oat_file_->GetLocation();642   OatClassType type = static_cast<OatClassType>(*reinterpret_cast<const uint16_t*>(type_pointer));643   CHECK_LT(type, kOatClassMax);//检查是否type<3644 645   const uint8_t* after_type_pointer = type_pointer + sizeof(int16_t);646   CHECK_LE(after_type_pointer, oat_file_->End()) << oat_file_->GetLocation();647 648   uint32_t bitmap_size = 0;649   const uint8_t* bitmap_pointer = nullptr;650   const uint8_t* methods_pointer = nullptr;651   if (type != kOatClassNoneCompiled) {652     if (type == kOatClassSomeCompiled) {653       bitmap_size = static_cast<uint32_t>(*reinterpret_cast<const uint32_t*>(after_type_pointer));654       bitmap_pointer = after_type_pointer + sizeof(bitmap_size);655       CHECK_LE(bitmap_pointer, oat_file_->End()) << oat_file_->GetLocation();656       methods_pointer = bitmap_pointer + bitmap_size;657     } else {658       methods_pointer = after_type_pointer;659     }660     CHECK_LE(methods_pointer, oat_file_->End()) << oat_file_->GetLocation();661   }662 663   return OatFile::OatClass(oat_file_,664                            status,665                            type,666                            bitmap_size,667                            reinterpret_cast<const uint32_t*>(bitmap_pointer),668                            reinterpret_cast<const OatMethodOffsets*>(methods_pointer));669 }

这个函数定义在文件art/runtime/oat_file.cc中。
参数class_def_index表示要查找的目标类的编号。这个编号用作数组oat_class_offsets_pointer_(即前面描述的methods_offsets_pointer数组)的索引,就可以得到一个偏移位置oat_class_offset。这个偏移位置是相对于OAT文件的oatdata段的,因此将该偏移值加上OAT文件的oatdata段的开始位置后,就可以得到目标类的所有方法的本地机器指令信息。
然后type_pointer是status_pointer + sizeof(uint16_t),则在oatdata段开始的前2个字节为oatclass的类型type,可能为kOatClassAllCompiled(0-oatclass后面就是每个method的OatMethodOffsets), kOatClassSomeCompiled(1 - 一个bitmap,显示哪些OatMethodOffsets放置在OatClass之后), kOatClassNoneCompiled(2 - 所有的methods都是要经过解释器的,所以没有OatMethodOffsets), kOatClassMax(3)。根据不同的type,具体内容也不同。增加了bitmap _pointer。

在OAT文件中,每一个DEX文件包含的每一个类的描述信息都通过一个OatClass对象来描述。为了方便描述,我们称之为OAT类。我们通过OatClass类的构造函数来理解它的作用,如下所示:

671 OatFile::OatClass::OatClass(const OatFile* oat_file,672                             mirror::Class::Status status,673                             OatClassType type,674                             uint32_t bitmap_size,675                             const uint32_t* bitmap_pointer,676                             const OatMethodOffsets* methods_pointer)677     : oat_file_(oat_file), status_(status), type_(type),678       bitmap_(bitmap_pointer), methods_pointer_(methods_pointer) {679     switch (type_) {680       case kOatClassAllCompiled: {681         CHECK_EQ(0U, bitmap_size);682         CHECK(bitmap_pointer == nullptr);683         CHECK(methods_pointer != nullptr);684         break;685       }686       case kOatClassSomeCompiled: {687         CHECK_NE(0U, bitmap_size);688         CHECK(bitmap_pointer != nullptr);689         CHECK(methods_pointer != nullptr);690         break;691       }692       case kOatClassNoneCompiled: {693         CHECK_EQ(0U, bitmap_size);694         CHECK(bitmap_pointer == nullptr);695         CHECK(methods_pointer_ == nullptr);696         break;697       }698       case kOatClassMax: {699         LOG(FATAL) << "Invalid OatClassType " << type_;700         break;701       }702     }703 }

参数oat_ file描述的是宿主OAT文件,参数status描述的是OAT类状态,type描述的是oatclass的类型,bitmap_ size描述的是如果部分编译,那么OatMethodOffsets对应的bitmap的大小,bitmap_ pointer也是一个数组,参数methods_pointer是一个数组,描述的是OAT类的各个方法的信息,它们被分别保存在OatClass类的相应成员变量中。通过这些信息,我们就可以获得包含在该DEX文件里面的类的所有方法的本地机器指令信息。
例如,通过调用OatClass类的成员函数GetOatMethod可以获得指定类方法的本地机器指令信息:

735 const OatFile::OatMethod OatFile::OatClass::GetOatMethod(uint32_t method_index) const {736   const OatMethodOffsets* oat_method_offsets = GetOatMethodOffsets(method_index);737   if (oat_method_offsets == nullptr) {738     return OatMethod(nullptr, 0);739   }740   if (oat_file_->IsExecutable() ||741       Runtime::Current() == nullptr ||        // This case applies for oatdump.742       Runtime::Current()->IsAotCompiler()) {743     return OatMethod(oat_file_->Begin(), oat_method_offsets->code_offset_);744   }745   // We aren't allowed to use the compiled code. We just force it down the interpreted / jit746   // version.747   return OatMethod(oat_file_->Begin(), 0);748 }

这个函数定义在文件art/runtime/oat_file.cc中。
参数method_index描述的目标方法在类中的编号,用这个编号作为索引,就可以在OatClass类的成员变量methods_pointer_指向的一个数组中找到目标方法的本地机器指令信息。这些本地机器指令信息封装在一个OatMethod对象。
分了三种情况,一种是oat_method_offsets为空,第二种是可执行或者oatdump或者确定是aot compiler的情况下,实例化一个oatmethod,第三种是不允许使用已编译好的代码,必须进入解释器。
为了进一步理解OatMethod的作用,我们继续看它的构造函数的实现,如下所示:

136     // Create an OatMethod with offsets relative to the given base address137     OatMethod(const uint8_t* base, const uint32_t code_offset)138         : begin_(base), code_offset_(code_offset) {139     }

这个函数定义在文件art/runtime/oat_file.h中。
OatMethod类的两个参数base和code_offset描述的信息:
参数base描述的是OAT文件的OAT头在内存的位置,而参数code_offset描述的是类方法的本地机器指令相对OAT头的偏移位置。将这两者相加,就可以得到一个类方法的本地机器指令在内存的位置。我们可以通过调用OatMethod类的成员函数GetCode来获得这个结果。
OatMethod类的成员函数GetQuickCode的实现如下所示:

107     const void* GetQuickCode() const {108       return GetOatPointer<const void*>(code_offset_);109     }

这个函数定义在文件art/runtime/oat_file.h中。
OatMethod类的成员函数调用另外一个成员函数GetOatPointer来获得一个类方法的本地机器指令在内存的位置。
OatMethod类的成员函数GetOatPointer的实现如下所示:

152     template<class T>153     T GetOatPointer(uint32_t offset) const {154       if (offset == 0) {155         return nullptr;156       }157       return reinterpret_cast<T>(begin_ + offset);158     }

这个函数定义在文件art/runtime/oat_file.h中。
这里写图片描述
(note:其实在oat class那个框里面还应该有type和bitmap。图示为type为kOatClassAllCompiled的情况)
我们从左往右来看图7。首先是根据类签名信息从包含在OAT文件里面的DEX文件中查找目标Class的编号,然后再根据这个编号找到在OAT文件中找到对应的OatClass。接下来再根据方法签名从包含在OAT文件里面的DEX文件中查找目标方法的编号,然后再根据这个编号在前面找到的OatClass中找到对应的OatMethod。有了这个OatMethod之后,我们就根据它的成员变量begin_和code_offset_找到目标类方法的本地机器指令了。其中,从DEX文件中根据签名找到类和方法的编号要求对DEX文件进行解析,这就需要利用Dalvik虚拟机的知识了。
至此,我们就通过OAT文件的加载过程分析完成OAT文件的格式了。

原创粉丝点击