主要参考: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 (as 115 // !executable is a sign that we may want to patch), which may not be allowed for 116 // various reasons. 117 if (kUseDlopen && (kIsTargetBuild || kUseDlopenOnHost) && executable) { 118 // Try to use dlopen. This may fail for various reasons, outlined below. We try dlopen, as 119 // 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 that 134 // open a generated dex file by name, remove the file, then open 135 // another generated dex file with the same name. http://b/10614658 136 // 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 usually 141 // 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 the 151 // ScopedLock, which we better not delete to avoid races. TODO: Investigate how to fix the API 152 // 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_OS 232 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 #else 236 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 end 145 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<3 644 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 / jit 746 // 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 address 137 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文件的格式了。