学习ART之生成Runtime OAT文件

来源:互联网 发布:保暖内衣选购知乎 编辑:程序博客网 时间:2024/04/29 17:48

从前一篇学习笔记——ART无缝对接Dalvik虚拟机中可以了解到Dalvik和ART这两种虚拟机机制的可执行文件存在着区别,ART对应的是OAT文件格式,而Dalvik则是dexy格式的文件,而且还知道将apk文件进行优化并转换为指定可执行文件的过程都是发生在应用程序安装的过程中,所不同的只是调用了不同的模块来进行转化而已。这次先来分析一下art虚拟机启动过程中生成oat文件的过程。

1.dex文件转化为oat文件

首先还是看一下讲dex转化成oat文件的代码,前两个参数就是输入的apk文件和输出的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));}

这里将会执行的时在/system/bin/dex2oat来对apk文件中的class.dex进行优化。这个程序的源代码文件在art/dex2oat/dex2oat.cc文件中,本人水平所限,里面转化的代码有点难看懂,暂时就先放在一边了。

2.JNI_CreateJavaVM

在创建ART虚拟机的过程中会进行第一次的dex到oat文件的转化,这里就先分析一下这个过程以便对整个过程有个基础的了解。在之前分析过的JniInvocation::Init函数中有如下一段代码:

 if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),                 "JNI_GetDefaultJavaVMInitArgs")) {    return false;  } if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),                 "JNI_CreateJavaVM")) {   return false; }       if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),                 "JNI_GetCreatedJavaVMs")) {   return false; } return true;

这段代码就是获得创建虚拟机所必须的接口函数指针,而这里的接口则是根据文件中定义klibararyFallback字符串来获取的,kitkat中是libdvm.so,而在最新的5.0 lollipop的代码中可以看到变成了libart.so,也就是说art模式已经成为默认的虚拟机了:


再回到kitkat的代码中,假设这里也是执行libart.so中的art虚拟机创建函数,下面就要来分析一下创建过程中解析oat文件的过程。在获得libart.so中的三个函数指针之后会调用JNI_CreateJavaVM来创建art虚拟机,这个函数在art/runtime/jni_internal.cc中:

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);  //判断JNI的版本,如果版本错误则直接返回当前版本  if (IsBadJniVersion(args->version)) {    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;    return JNI_EVERSION;  }  Runtime::Options options;  //将第三个参数中的option项取出  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;  //调用Runtime::Create创建一个新的Runtime实例并进行初始化  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;}

3.Runtime.Create和Runtime.Init

Runtime.Create这个函数在art/runtime/runtime.cc文件的Runtime类中,instance_中存储着Runtime的实例,如果不存在Runtime实例才会进行创建。在new了一个Runtime实例之后调用Init函数对其进行初始化:

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;}

接下去再来分析一下Init函数,这个函数还在同一个文件中,

bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {  ... ....  //根据传入的options参数创建一个ParsedOptions对象  UniquePtr<ParsedOptions> options(ParsedOptions::Create(raw_options, ignore_unrecognized));  ... ...  //根据新创建的ParsedOptions对象的值初始化Runtime实例中的一些值  host_prefix_ = options->host_prefix_;  boot_class_path_string_ = options->boot_class_path_string_;  class_path_string_ = options->class_path_string_;  properties_ = options->properties_;  ... ...  //同样时根据ParsedOptions的参数创建一个新的堆  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_);  //创建一个JavaVMExt对象,这个对象后面可以通过GetJavaVM函数获得,以便外部程序和虚拟机进行交互  java_vm_ = new JavaVMExt(this, options.get());  //根据当前的线程列表和虚拟机环境创建一个新线程并设为主线程  Thread* self = Thread::Attach("main", false, NULL, false);  CHECK_EQ(self->thin_lock_id_, ThreadList::kMainId);  CHECK(self != NULL);  ... ...   //GetHeap函数获得上面新创建的堆指针,GetContinuousSpaces函数获得堆中的连续空间,判断这个连续空间是不是Image空间,如果是则调用CreateFromImage函数来创建一个ClassLinker对象,否则调用CreateFromCompiler来创建这个对象  if (GetHeap()->GetContinuousSpaces()[0]->IsImageSpace()) {    class_linker_ = ClassLinker::CreateFromImage(intern_table_);  } else {    CHECK(options->boot_class_path_ != NULL);    CHECK_NE(options->boot_class_path_->size(), 0U);    class_linker_ = ClassLinker::CreateFromCompiler(*options->boot_class_path_, intern_table_);  }  ... ...  return true;}

4.ParsedOptions::Create和gc::Heap::Heap

在上面的函数中有两个还需要深入分析的,首先就是ParsedOptions::Create,这个函数仍然是在runtime.cc文件中,它通过传入的option参数创建一个ParsedOptions对象,这个对象中的值在后面的进程中起到了非常重要的作用,部分代码如下:

Runtime::ParsedOptions* Runtime::ParsedOptions::Create(const Options& options, bool ignore_unrecognized) {  //从环境变量中获取到启动类的默认路径  const char* boot_class_path_string = getenv("BOOTCLASSPATH");  if (boot_class_path_string != NULL) {    parsed->boot_class_path_string_ = boot_class_path_string;  }  ... ...  //is_compiler_用于定义当前的art虚拟机时用来编译dex文件到oat文件的还是第一次启动虚拟机时调用的,默认为后者  parsed->is_compiler_ = false;  ... ...  //遍历所有选项,对每个选项进行针对性的处理  for (size_t i = 0; i < options.size(); ++i) {    const std::string option(options[i].first);    //启动路径的参数     if (StartsWith(option, "-Xbootclasspath:")) {      parsed->boot_class_path_string_ = option.substr(strlen("-Xbootclasspath:")).data();      }     //这个参数是用来指定创建虚拟机的image所在的路径     else if (StartsWith(option, "-Ximage:")) {      parsed->image_ = option.substr(strlen("-Ximage:")).data();      }     ... ...     else if (option == "compiler") {      parsed->is_compiler_ = true;      }     ... ...     //如果既不是用于编译的虚拟机也不存在创建虚拟机所必须的镜像直接把镜像路径指定为预先定义的位置,也就是/system/framework/boot.art     if (!parsed->is_compiler_ && parsed->image_.empty()) {    parsed->image_ += GetAndroidRoot();    parsed->image_ += "/framework/boot.art";    }  }}

目前就关注以上这几个参数:
    1.-Xbootclasspath参数用于指定Runtime需要用到的生成oat文件的dex文件路径。也就是这个路径下存在着一些dex文件,用于生成Runtime自己的oat文件,这个会在后面提到。
    2.compiler参数用于指定当前是否是安装应用程序时所调用的art虚拟机。在应用程序安装的时候会fork一个虚拟机用来将apk文件中的classes.dex翻译成oat文件格式。
    3.-Ximage参数用语指定将要生成的art虚拟机image镜像文件所在的路径,如果不存在则采用默认路径/system/framework/boot.art,当然这个文件也有可能不存在,如果是这样就会生成一个image文件,这个在后面会提到。

下面再来看Heap的构造函数,这个函数也比较大,在art/runtime/gc/heap.cc文件中,目前就只看关于启动image的那部分,这里会根据传入的image镜像的路径(就是ParsedOptions::Create函数中的获得的image参数)去获取这个image文件,然后根据这个文件创建第一个连续的堆空间:

Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,           double target_utilization, size_t capacity, const std::string& original_image_file_name,           bool concurrent_gc, size_t parallel_gc_threads, size_t conc_gc_threads,           bool low_memory_mode, size_t long_pause_log_threshold, size_t long_gc_log_threshold,           bool ignore_max_footprint){ ... ... std::string image_file_name(original_image_file_name);  if (!image_file_name.empty()) {    space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name);    ... ...    AddContinuousSpace(image_space);    ... ...  } ... ...}

5.ImageSpace::Create

再来看ImageSpace::Create函数,这个函数在art/runtime/gc/space/image_space.cc文件中,其根据image镜像初始化第一块连续堆内存,它会依次从指定路径寻找image文件,全都不存在时便会自己生成一个并进行初始化:

ImageSpace* ImageSpace::Create(const std::string& original_image_file_name) {  ///system目录下的镜像文件存在  if (OS::FileExists(original_image_file_name.c_str())) {    return space::ImageSpace::Init(original_image_file_name, false);  }  ///system文件不存在则从/data/dalvik-cache查找名为system@framework@boot.art@classes.dex(文件名由原路径的/替换为@并在后面加上classes.dex)文件,找到则直接初始化  std::string image_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) {      return image_space;    }  }  //调用GenerateImage函数生成一个镜像文件随后进行初始化  CHECK(GenerateImage(image_file_name)) << "Failed to generate image: " << image_file_name;  return space::ImageSpace::Init(image_file_name, true);}

6.ImageSpace::Init和GenerateImage

在ImageSpace::Create函数在art/runtime/gc/space/image_space.cc文件中,部分代码如下,首先会打开image文件并将文件内容映射到内存中,随后根据image文件的头部获得Runtime oat文件的位置,经过对oat文件的一系列校验后打开:

ImageSpace* ImageSpace::Init(const std::string& image_file_name, bool validate_oat_file) {  ... ...  //打开image镜像文件  UniquePtr<File> file(OS::OpenFileForReading(image_file_name.c_str()));  ... ...  ImageHeader image_header;  //获取image文件的头部  bool success = file->ReadFully(&image_header, sizeof(image_header));  ... ...  //下面的代码就是将image文件以及它的位图文件映射到内存空间了  UniquePtr<MemMap> map(MemMap::MapFileAtAddress(image_header.GetImageBegin(),                                                 image_header.GetImageSize(),                                                 PROT_READ | PROT_WRITE,                                                 MAP_PRIVATE | MAP_FIXED,                                                 file->Fd(),                                                 0,                                                 false));  ... ...    std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", image_file_name.c_str(),                                       bitmap_index));  UniquePtr<accounting::SpaceBitmap> bitmap(      accounting::SpaceBitmap::CreateFromMemMap(bitmap_name, image_map.release(),                                                reinterpret_cast<byte*>(map->Begin()),                                                map->Size())); ... ...  UniquePtr<ImageSpace> space(new ImageSpace(image_file_name, map.release(), bitmap.release()));  if (kIsDebugBuild) {    space->VerifyImageAllocations();  }  //根据image的文件头获取到Runtime oat文件  space->oat_file_.reset(space->OpenOatFile());  ... ...   return space.release();}

上一个函数是在指定路径下存在image文件直接调用Init函数进行初始化,而当image文件不存在时则会先调用GenerateImage是创建一个image文件,这个函数的代码在art/runtime/gc/space/image_space.cc中.通过构造参数,调用dex2oat程序并从指定的jar中获取信息生成image文件,同时由boot_class_path_string路径下的dex文件生成对应Runtime的oat文件:

static bool GenerateImage(const std::string& image_file_name) {  const std::string boot_class_path_string(Runtime::Current()->GetBootClassPathString());  std::vector<std::string> boot_class_path;  Split(boot_class_path_string, ':', boot_class_path);  if (boot_class_path.empty()) {    LOG(FATAL) << "Failed to generate image because no boot class path specified";  }  std::vector<std::string> arg_vector;  //构造命令行参数,选择哪一个dex2oat执行生成image文件  std::string dex2oat(GetAndroidRoot());  dex2oat += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");  arg_vector.push_back(dex2oat);  //生成的image文件system@framework@boot.art@classes.dex通过--image参数传入  std::string image_option_string("--image=");  image_option_string += image_file_name;  arg_vector.push_back(image_option_string);  ... ...  //需要加入到oat文件中的dex文件加入到参数中,dex2oat将这些dex文件翻译成本地指令并集成到同一个oat文件中  for (size_t i = 0; i < boot_class_path.size(); i++) {    arg_vector.push_back(std::string("--dex-file=") + boot_class_path[i]);  }  //除了生成image文件外,还会生成一个以oat为后缀的system@framework@boot.art@classes.oat文件  std::string oat_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);  arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS));  //生成image文件需要预加载的类就在这里指定  if (kIsTargetBuild) {    arg_vector.push_back("--image-classes-zip=/system/framework/framework.jar");    arg_vector.push_back("--image-classes=preloaded-classes");  } else {    arg_vector.push_back("--host");  }  ... ...  std::vector<char*> char_args;  //vector<string>参数列表转换为vector<char*>形式的列表  for (std::vector<std::string>::iterator it = arg_vector.begin(); it != arg_vector.end();      ++it) {    char_args.push_back(const_cast<char*>(it->c_str()));  }  char_args.push_back(NULL);  // fork and exec dex2oat  pid_t pid = fork();  if (pid == 0) {    setpgid(0, 0);    //执行生成image文件和oat文件的指令    execv(dex2oat.c_str(), &char_args[0]);    PLOG(FATAL) << "execv(" << dex2oat << ") failed";    return false;  } else {    ... ...   }  return true;}

无论如何,最终art虚拟机在创建过程中都会进行image文件的映射以及打开Runtime oat文件。

最后再来梳理一下整个过程:
1.在启动Zygote进程之前需要调用libart.so中的JNI_CreateJavaVM创建虚拟机
2.创建虚拟机时需要对Runtime运行时进行初始化操作
3.Runtime初始化的过程中会创建虚拟机所需要的堆空间并使用image镜像文件堆首块堆连续内存进行初始化并获得Runtime所必须的oat文件

下一次将会分析art虚拟机解析oat文件的过程和oat文件格式。


0 0