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

本文深入分析了Android ART运行时加载OAT文件的过程,包括OAT文件的结构、生成及加载机制。从 Dex 文件翻译到本地机器指令,再到OAT文件在内存中的布局,详细阐述了OAT头、Image空间文件路径和DEX文件描述信息。通过OAT文件的加载,读者可以了解到如何找到类方法的本地机器指令,为理解ART运行时工作原理打下基础。
摘要由CSDN通过智能技术生成
               

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

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

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

图1 OAT文件结构

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

       作为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是如何产生的。如前面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+8dig      char 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文件里面的classes.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。

        Runtime类的静态成员函数Create负责在进程中创建一个ART虚拟机。创建成功后,就调用Runtime类的另外一个静态成员函数Start启动该ART虚拟机。注意,这个创建ART虚拟的动作只会在Zygote进程中执行,SystemServer系统进程以及Android应用程序进程的ART虚拟机都是直接从Zygote进程fork出来共享的。这与Dalvik虚拟机的创建方式是完全一样的。

       接下来我们就重点分析Runtime类的静态成员函数Create,它的实现如下所示:

bool Runtime::Create(const Options& options, bool ignore_unrecognized) {  // TODO: acquire a static mutex on Runtime to avoid racing.  if (Runtime::instance_ != NULL) {    return false;  }  InitLogging(NULL);  // Calls Locks::Init() as a side effect.  instance_ = new Runtime;  if (!instance_->Init(options, ignore_unrecognized)) {    delete instance_;    instance_ = NULL;    return false;  }  return true;}
       这个函数定义在文件art/runtime/runtime.cc中。

       instance_是Runtime类的静态成员变量,它指向进程中的一个Runtime单例。这个Runtime单例描述的就是当前进程的ART虚拟机实例。

       函数首先判断当前进程是否已经创建有一个ART虚拟机实例了。如果有的话,函数就立即返回。否则的话,就创建一个ART虚拟机实例,并且保存在Runtime类的静态成员变量instance_中,最后调用Runtime类的成员函数Init对该新创建的ART虚拟机进行初始化。

       Runtime类的成员函数Init的实现如下所示:

bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {  ......  UniquePtr<ParsedOptions> options(ParsedOptions::Create(raw_options, ignore_unrecognized));  ......  heap_ = new gc::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_ = new JavaVMExt(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_);  }  ......  return true;}
       这个函数定义在文件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来创建一个Clas

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值