当前位置: 首页 > 工具软件 > oat++ > 使用案例 >

ART加载OAT文件的过程分析

安浩瀚
2023-12-01

  OAT文件是一种Android引入ART虚拟机后的一种私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。Android使用/system/bin/dex2oat(我们也可以编译出debug版本的dex2oatd)来将DEX文件编译成OAT文件,dex2oat的主入口为main函数:

/art/dex2oat/dex2oat.cc

int main(int argc, char** argv) {
  return art::dex2oat(argc, argv);
}

  dex2oat既可以编译出app的OAT文件,也可以编译出boot.oat和boot.art文件,这个是由传入的”–image=”参数决定。先说下编译出boot.oat和boot.art的流程。在host上生成boot.oat和boot.art的过程中,我们获得的参数是:

dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[0]=--runtime-arg
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:1059] dex2oat: option[1]=-Xms64m
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[2]=--runtime-arg
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:1059] dex2oat: option[3]=-Xmx64m
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[4]=--image-classes=frameworks/base/preloaded-classes
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[5]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/com.mstar.android_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[6]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/ngbj_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[7]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/tvm_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[8]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[9]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/conscrypt_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[10]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/okhttp_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[11]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/core-junit_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[12]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/bouncycastle_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[13]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[14]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[15]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[16]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/voip-common_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[17]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/ims-common_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[18]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/mms-common_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[19]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/android.policy_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[20]=--dex-file=out/target/common/obj/JAVA_LIBRARIES/apache-xml_intermediates/javalib.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[21]=--dex-location=/system/framework/com.mstar.android.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[22]=--dex-location=/system/framework/ngbj.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[23]=--dex-location=/system/framework/tvm.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[24]=--dex-location=/system/framework/core-libart.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[25]=--dex-location=/system/framework/conscrypt.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[26]=--dex-location=/system/framework/okhttp.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[27]=--dex-location=/system/framework/core-junit.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[28]=--dex-location=/system/framework/bouncycastle.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[29]=--dex-location=/system/framework/ext.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[30]=--dex-location=/system/framework/framework.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[31]=--dex-location=/system/framework/telephony-common.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[32]=--dex-location=/system/framework/voip-common.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[33]=--dex-location=/system/framework/ims-common.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[34]=--dex-location=/system/framework/mms-common.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[35]=--dex-location=/system/framework/android.policy.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[36]=--dex-location=/system/framework/apache-xml.jar
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[37]=--oat-symbols=out/target/product/ponkan/symbols/system/framework/arm/boot.oat
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[38]=--oat-file=out/target/product/ponkan/dex_bootjars/system/framework/arm/boot.oat
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[39]=--oat-location=/system/framework/arm/boot.oat
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[40]=--image=out/target/product/ponkan/dex_bootjars/system/framework/arm/boot.art
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[41]=--base=0x70000000
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[42]=--instruction-set=arm
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[43]=--instruction-set-features=div
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[44]=--android-root=out/target/product/ponkan/system
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[45]=--include-patch-information
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[46]=--runtime-arg
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:1059] dex2oat: option[47]=-Xnorelocate
dex2oatd I 21068 21068 art/dex2oat/dex2oat.cc:915] dex2oat: option[48]=--no-include-debug-symbols

  解释一下这些参数的意义:
–runtime-arg:表示下一个参数是一个运行时参数,这些参数都会用于创建dex2oat进程内部的art虚拟机。
–image-classes:参数值表示要被编到image里面的一些类的路径,这里是Zygote用到的预加载的类,在host的路径是frameworks/base/preloaded-classes,在target的路径是/system/etc/preloaded-classes。
–dex-file:表示要进行编译的dex文件,jar文件或apk文件
–dex-location:与”–dex-file”参数一一对应的jar文件(对应设备上的路径)
–oat-symbols:带symbol的oat文件路径
–oat-file:输出的oat文件名
–oat-location:输出的oat文件的路径(对应设备上的路径)
–image:生成的image的路径
–base:偏移基址
–instruction-set:指令集
–instruction-set-features:指令集参数
–android-root:portable linking所用的库的路径
–include-patch-information:编译时包含patch信息,可以在不重编的情况下重定位
–no-include-debug-symbols:不包含debug用的symbol
  上面是在host上编译出boot.oat和boot.art,最终放到设备的/system/framework/arm(arm64)/下。但有时host上并没有编译出这两个文件,那么编译步骤需要在运行时进行。
  Zygote中启动的运行时使用”-Ximage:”指定启动用的image名字。当没有指定该参数时,将指定的image设置为”/system/framework/boot.art”。image_file_name不为空,便会调用ImageSpace::Create创建boot.oat和boot.art。

/art/runtime/gc/heap.cc

  //image_file_name为image的名字
  if (!image_file_name.empty()) {
    std::string error_msg;
    space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
                                                               image_instruction_set,
                                                               &error_msg);

/art/runtime/gc/space/image_space.cc

ImageSpace* ImageSpace::Create(const char* image_location,
                               const InstructionSet image_isa,
                               std::string* error_msg) {
  std::string system_filename;
  bool has_system = false;
  std::string cache_filename;
  bool has_cache = false;
  bool dalvik_cache_exists = false;
  bool is_global_cache = true;
  const bool found_image = FindImageFilename(image_location, image_isa, &system_filename,
                                             &has_system, &cache_filename, &dalvik_cache_exists,
                                             &has_cache, &is_global_cache);

  ImageSpace* space;
  bool relocate = Runtime::Current()->ShouldRelocate();
  bool can_compile = Runtime::Current()->IsImageDex2OatEnabled();
  //若在/system/framework/arm(arm64)找到boot.art或者在/data/dalvik-cache/arm(arm64)找到system@framework@boot.art,found_image为true
  if (found_image) {
    const std::string* image_filename;
    bool is_system = false;
    bool relocated_version_used = false;
    //relocate重定位用于首次开机
    if (relocate) {
      //找不到/data/dalvik-cache/arm(arm64)目录直接返回null
      if (!dalvik_cache_exists) {
        *error_msg = StringPrintf("Requiring relocation for image '%s' at '%s' but we do not have "
                                  "any dalvik_cache to find/place it in.",
                                  image_location, system_filename.c_str());
        return nullptr;
      }
      //若在/system/framework/arm(arm64)找到boot.art,has_system为true
      if (has_system) {
        //同时在/data/dalvik-cache/arm(arm64)找到system@framework@boot.art且checksum匹配
        if (has_cache && ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) {
          // We already have a relocated version
          //将/data/dalvik-cache/arm(arm64)下的image文件作为启动image
          image_filename = &cache_filename;
          relocated_version_used = true;
        } else {//system目录下有image,而/data目录下没有image或者image不匹配checksum
          // We cannot have a relocated version, Relocate the system one and use it.

          std::string reason;
          bool success;

          // Check whether we are allowed to relocate.
          if (!can_compile) {
            reason = "Image dex2oat disabled by -Xnoimage-dex2oat.";
            success = false;
          } else if (!ImageCreationAllowed(is_global_cache, &reason)) {
            // Whether we can write to the cache.
            success = false;
          } else {
            //relocate system下的image到/data/dalvik-cache/arm(arm64)下
            // Try to relocate.
            success = RelocateImage(image_location, cache_filename.c_str(), image_isa, &reason);
          }

          if (success) {
            //relocate成功,将/data下的image作为启动image
            relocated_version_used = true;
            image_filename = &cache_filename;
          } else {
            *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s': %s",
                                      image_location, system_filename.c_str(),
                                      cache_filename.c_str(), reason.c_str());
            // We failed to create files, remove any possibly garbage output.
            // Since ImageCreationAllowed was true above, we are the zygote
            // and therefore the only process expected to generate these for
            // the device.
            PruneDexCache(image_isa);
            return nullptr;
          }
        }
      } else {//system下没有image,这是data下一定有image
        CHECK(has_cache);
        // We can just use cache's since it should be fine. This might or might not be relocated.
        //依然优先使用/data下的image作为启动image
        image_filename = &cache_filename;
      }
    } else {//非relocate情况
      //data和system下都有image
      if (has_system && has_cache) {
        // Check they have the same cksum. If they do use the cache. Otherwise system.
        if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) {
          //checksum匹配的话优先使用/data下面的image
          image_filename = &cache_filename;
          relocated_version_used = true;
        } else {
          //checksum不匹配则使用/system下面的image
          image_filename = &system_filename;
          is_system = true;
        }
      } else if (has_system) {//只有/system下面的image,则使用这个image
        image_filename = &system_filename;
        is_system = true;
      } else {//使用/data下面的image
        CHECK(has_cache);
        image_filename = &cache_filename;
      }
    }
    {
      // Note that we must not use the file descriptor associated with
      // ScopedFlock::GetFile to Init the image file. We want the file
      // descriptor (and the associated exclusive lock) to be released when
      // we leave Create.
      ScopedFlock image_lock;
      image_lock.Init(image_filename->c_str(), error_msg);
      VLOG(startup) << "Using image file " << image_filename->c_str() << " for image location "
                    << image_location;
      // If we are in /system we can assume the image is good. We can also
      // assume this if we are using a relocated image (i.e. image checksum
      // matches) since this is only different by the offset. We need this to
      // make sure that host tests continue to work.
      //使用之前定下来的image文件进行imagespace的初始化操作
      space = ImageSpace::Init(image_filename->c_str(), image_location,
                               !(is_system || relocated_version_used), error_msg);
    }
    if (space != nullptr) {
      return space;
    }

    if (relocated_version_used) {
      // Something is wrong with the relocated copy (even though checksums match). Cleanup.
      // This can happen if the .oat is corrupt, since the above only checks the .art checksums.
      // TODO: Check the oat file validity earlier.
      *error_msg = StringPrintf("Attempted to use relocated version of %s at %s generated from %s "
                                "but image failed to load: %s",
                                image_location, cache_filename.c_str(), system_filename.c_str(),
                                error_msg->c_str());
      PruneDexCache(image_isa);
      return nullptr;
    } else if (is_system) {
      // If the /system file exists, it should be up-to-date, don't try to generate it.
      *error_msg = StringPrintf("Failed to load /system image '%s': %s",
                                image_filename->c_str(), error_msg->c_str());
      return nullptr;
    } else {
      // Otherwise, log a warning and fall through to GenerateImage.
      LOG(WARNING) << *error_msg;
    }
  }
  //以下是/data和/system下面都没image的情况
  if (!can_compile) {//需要允许dex2oat
    *error_msg = "Not attempting to compile image because -Xnoimage-dex2oat";
    return nullptr;
  } else if (!dalvik_cache_exists) {//data/dalvik-cache/arm(arm64)目录必须存在
    *error_msg = StringPrintf("No place to put generated image.");
    return nullptr;
  } else if (!ImageCreationAllowed(is_global_cache, error_msg)) {//允许创建image
    return nullptr;
  } else if (!GenerateImage(cache_filename, image_isa, error_msg)) {
    //创建boot.art和boot.oat到/data/dalvik-cache/arm(arm64)下
    *error_msg = StringPrintf("Failed to generate image '%s': %s",
                              cache_filename.c_str(), error_msg->c_str());
    // We failed to create files, remove any possibly garbage output.
    // Since ImageCreationAllowed was true above, we are the zygote
    // and therefore the only process expected to generate these for
    // the device.
    PruneDexCache(image_isa);
    return nullptr;
  } else {
    // Check whether there is enough space left over after we have generated the image.
    if (!CheckSpace(cache_filename, error_msg)) {
      // No. Delete the generated image and try to run out of the dex files.
      PruneDexCache(image_isa);
      return nullptr;
    }

    // Note that we must not use the file descriptor associated with
    // ScopedFlock::GetFile to Init the image file. We want the file
    // descriptor (and the associated exclusive lock) to be released when
    // we leave Create.
    ScopedFlock image_lock;
    image_lock.Init(cache_filename.c_str(), error_msg);
    //使用创建的image初始化imagespace
    space = ImageSpace::Init(cache_filename.c_str(), image_location, true, error_msg);
    if (space == nullptr) {
      *error_msg = StringPrintf("Failed to load generated image '%s': %s",
                                cache_filename.c_str(), error_msg->c_str());
    }
    return space;
  }
}

  如果没有使用”-Xbootclasspath:”参数特别指定的话,运行时的bootclasspath就是变量BOOTCLASSPATH的值。可以看到,GenerateImage就是生成dex2oat的各种参数,传给exec命令执行。

18:46:07:223I/art     ( 1953): GenerateImage: /system/bin/dex2oat --image=/data/dalvik-cache/arm/system@framework@boot.art --dex-file=/system/framework/com.mstar.android.jar --dex-file=/system/framework/ngbj.jar --dex-file=/system/framework/tvm.jar --dex-file=/system/framework/core-libart.jar --dex-file=/system/framework/conscrypt.jar --dex-file=/system/framework/okhttp.jar --dex-file=/system/framework/core-junit.jar --dex-file=/system/framework/bouncycastle.jar --dex-file=/system/framework/ext.jar --dex-file=/system/framework/framework.jar --dex-file=/system/framework/telephony-common.jar --dex-file=/system/framework/voip-common.jar --dex-file=/system/framework/ims-common.jar --dex-file=/system/framework/mms-common.jar --dex-file=/system/framework/android.policy.jar --dex-file=/system/framework/apache-xml.jar --oat-file=/data/dalvik-cache/arm/system@framework@boot.oat --instruction-set=arm --instruction-set-features=div --base=0x70a72000 --runtime-arg -Xms64m --runtime-arg -Xmx64m --image-classes=/system/etc/preloaded-classes

/art/runtime/gc/space/image_space.cc

static bool GenerateImage(const std::string& image_filename, InstructionSet image_isa,
                          std::string* error_msg) {
  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()) {
    *error_msg = "Failed to generate image because no boot class path specified";
    return false;
  }
  // We should clean up so we are more likely to have room for the image.
  if (Runtime::Current()->IsZygote()) {
    LOG(INFO) << "Pruning dalvik-cache since we are generating an image and will need to recompile";
    PruneDexCache(image_isa);
  }

  std::vector<std::string> arg_vector;

  std::string dex2oat(Runtime::Current()->GetCompilerExecutable());
  arg_vector.push_back(dex2oat);// /system/bin/dex2oat或者/system/bin/dex2oatd(debug版本)为第一个参数

  std::string image_option_string("--image=");
  image_option_string += image_filename;
  arg_vector.push_back(image_option_string);
  // /data/dalvik-cache/arm(arm64)/system@framework@boot.art为第二个参数
  //后续的几个参数为"--dex-file=xxx",xxx分别是bootclasspath的以冒号分隔的值
  for (size_t i = 0; i < boot_class_path.size(); i++) {
    arg_vector.push_back(std::string("--dex-file=") + boot_class_path[i]);
  }
  // 这个参数是"--oat-file=/data/dalvik-cache/arm(arm64)/system@framework@boot.oat"
  std::string oat_file_option_string("--oat-file=");
  oat_file_option_string += ImageHeader::GetOatLocationFromImageLocation(image_filename);
  arg_vector.push_back(oat_file_option_string);

  //一些编译及指令集相关的参数
  Runtime::Current()->AddCurrentRuntimeFeaturesAsDex2OatArguments(&arg_vector);
  CHECK_EQ(image_isa, kRuntimeISA)
      << "We should always be generating an image for the current isa.";
  //选择一个合适的基址
  int32_t base_offset = ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA,
                                                    ART_BASE_ADDRESS_MAX_DELTA);
  LOG(INFO) << "Using an offset of 0x" << std::hex << base_offset << " from default "
            << "art base address of 0x" << std::hex << ART_BASE_ADDRESS;
  arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS + base_offset));

  if (!kIsTargetBuild) {
    arg_vector.push_back("--host");
  }

  const std::vector<std::string>& compiler_options = Runtime::Current()->GetImageCompilerOptions();
  for (size_t i = 0; i < compiler_options.size(); ++i) {
    arg_vector.push_back(compiler_options[i].c_str());
  }

  std::string command_line(Join(arg_vector, ' '));
  LOG(INFO) << "GenerateImage: " << command_line;
  return Exec(arg_vector, error_msg);
}

  回到dex2oat的执行过程。简单来说,就是解释参数->创建内部虚拟机->编译生成oat文件->生成image文件。

/art/dex2oat/dex2oat.cc

  //这里创建dex2oat内部虚拟机
  if (!Dex2Oat::Create(&p_dex2oat,
                       runtime_options,
                       *compiler_options,
                       compiler_kind,
                       instruction_set,
                       instruction_set_features,
                       verification_results.get(),
                       &method_inliner_map,
                       thread_count)) {
    LOG(ERROR) << "Failed to create dex2oat";
    timings.EndTiming();
    oat_file->Erase();
    return EXIT_FAILURE;
  }
  ...
  //编译生成oat文件
  std::unique_ptr<const CompilerDriver> compiler(dex2oat->CreateOatFile(boot_image_option,
                                                                        android_root,
                                                                        is_host,
                                                                        dex_files,
                                                                        oat_file.get(),
                                                                        oat_location,
                                                                        bitcode_filename,
                                                                        image,
                                                                        image_classes,
                                                                        compiled_classes,
                                                                        dump_stats,
                                                                        dump_passes,
                                                                        timings,
                                                                        compiler_phases_timings,
                                                                        swap_fd,
                                                                        profile_file,
                                                                        key_value_store.get()));
   ...
   //生成image文件(需要指定"--image="参数)
   if (image) {
    TimingLogger::ScopedTiming t("dex2oat ImageWriter", &timings);
    bool image_creation_success = dex2oat->CreateImageFile(image_filename,
                                                           image_base,
                                                           oat_unstripped,
                                                           oat_location,
                                                           *compiler.get());
 类似资料: