Android运行时ART加载OAT文件的过程分析

在前面一文中,我们介绍了Android运行时ART,它的核心是OAT文件。OAT文件是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。这使得我们无需重新编译原有的APK就可以让它正常地在ART里面运行,也就是我们不需要改变原来的APK编程接口。本文我们通过OAT文件的加载过程分析OAT文件的结构,为后面分析ART的工作原理打基础。

老罗的新浪微博:,欢迎关注!

OAT文件的结构如图1所示:

图1 OAT文件结构

由于OAT文件本质上是一个ELF文件,因此在最外层它具有一般ELF文件的结构,例如它有标准的ELF文件头以及通过段(Section)来描述文件内容。关于ELF文件的更多知识,可以参考维基百科:。

作为Android私有的一种ELF文件,OAT文件包含有两个特殊的段oatdata和oatexec,前者包含有用来生成本地机器指令的dex文件内容,后者包含有生成的本地机器指令,它们之间的关系通过储存在oatdata段前面的oat头部描述。此外,在OAT文件的dynamic段,导出了三个符号oatdata、oatexec和oatlastword,它们的值就是用来界定oatdata段和oatexec段的起止位置的。其中,[oatdata, oatexec – 4]描述的是oatdata段的起止位置,而[oatexec, oatlastword]描述的是oatlastword的起止位置。要完全理解OAT的文件格式,除了要理解本文即将要分析的OAT加载过程之外,还需要掌握接下来文章分析的类和方法查找过程。

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

static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,const char* output_file_name, const char* dexopt_flags) {static const char* DEX2OAT_BIN = "/system/bin/dex2oat";static const int MAX_INT_LEN = 12;// ‘-‘+10dig+’\0’ -OR- 0x+8digchar zip_fd_arg[strlen("–zip-fd=") + MAX_INT_LEN];char zip_location_arg[strlen("–zip-location=") + PKG_PATH_MAX];char oat_fd_arg[strlen("–oat-fd=") + MAX_INT_LEN];char oat_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 %s in=%s out=%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文件里面的classex.dex文件。实际上,一个OAT文件是可以由若干个DEX生成的。这意味着在生成的OAT文件的oatdata段中,包含有多个DEX文件。那么,在什么情况下,会生成包含多个DEX文件的OAT文件呢?

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

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); if (IsBadJniVersion(args->version)) {LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;return JNI_EVERSION; } Runtime::Options options; for (int i = 0; i < args->nOptions; ++i) {JavaVMOption* option = &args->options[i];options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo)); } bool ignore_unrecognized = args->ignoreUnrecognized; if (!Runtime::Create(options, ignore_unrecognized)) {return JNI_ERR; } Runtime* runtime = Runtime::Current(); bool started = runtime->Start(); if (!started) {delete Thread::Current()->GetJniEnv();delete runtime->GetJavaVM();LOG(WARNING) << "CreateJavaVM failed";return JNI_ERR; } *p_env = Thread::Current()->GetJniEnv(); *p_vm = runtime->GetJavaVM(); return JNI_OK;} 这个函数定义在文件art/runtime/jni_internal.cc中。

参数vm_args用作ART虚拟机的启动参数,它被转换为一个JavaVMInitArgs对象后,再按照Key-Value的组织形式保存一个Options向量中,并且作该向量作为参数传递给Runtime类的静态成员函数Create。

往事是尘封在记忆中的梦,而你是我唯一鲜明的记忆,

Android运行时ART加载OAT文件的过程分析

相关文章:

你感兴趣的文章:

标签云: