1
源码分析基于的是Android6.0的代码

什么是dex2oat

  • dex2oat是ART虚拟机必备的一个组件,主要用来把安装的apk和动态加载的dex等文件转换成oat文件。
  • 源码位置:art\dex2oat\Dex2oat.cc(只有一个文件)
  • 先看两张图(实在找不到最初的出处,感谢原始作者)
clipboard.png clipboard.png

dex2oat源码分析

Linus: Read The Fucking Source Code

dex2oat.cc文件的结构

namespace art{
...
class Dex2Oat FINAL{
...
};
class WatchDog{
...
};
static int dex2oat(int argc, char** argv);

}

int main(){
}

从main函数开始(入口)

int main(int argc, char** argv) {
  int result = art::dex2oat(argc, argv);
  // Everything was done, do an explicit exit here to avoid running Runtime destructors that take
  // time (bug 10645725) unless we're a debug build or running on valgrind. Note: The Dex2Oat class
  // should not destruct the runtime in this case.
  if (!art::kIsDebugBuild && (RUNNING_ON_VALGRIND == 0)) {
    exit(result);
  }
  return result;
}
作用:很明显,调用了art命名空间中的dex2oat方法,真正的实现在这个方法中。除非是调试构建或者在Valgrind上运行,否则在完成以后需要显示地退出,避免运行运行时的析构函数(bug 10645725)。

dex2oat方法

static int dex2oat(int argc, char** argv) {
  b13564922();

  TimingLogger timings("compiler", false, false);

  Dex2Oat dex2oat(&timings);

  // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError.
  dex2oat.ParseArgs(argc, argv);

  // Check early that the result of compilation can be written
  if (!dex2oat.OpenFile()) {
    return EXIT_FAILURE;
  }

  // Print the complete line when any of the following is true:
  //   1) Debug build
  //   2) Compiling an image
  //   3) Compiling with --host
  //   4) Compiling on the host (not a target build)
  // Otherwise, print a stripped command line.
  if (kIsDebugBuild || dex2oat.IsImage() || dex2oat.IsHost() || !kIsTargetBuild) {
    LOG(INFO) << CommandLine();
  } else {
    LOG(INFO) << StrippedCommandLine();
  }

  if (!dex2oat.Setup()) {
    dex2oat.EraseOatFile();
    return EXIT_FAILURE;
  }

  if (dex2oat.IsImage()) {
    return CompileImage(dex2oat);
  } else {
    return CompileApp(dex2oat);
  }
}
注意:这个方法并不在Dex2Oat类中。

1. b13564922()

static void b13564922() {
#if defined(__linux__) && defined(__arm__)
  int major, minor;
  struct utsname uts;
  if (uname(&uts) != -1 &&
      sscanf(uts.release, "%d.%d", &major, &minor) == 2 &&
      ((major < 3) || ((major == 3) && (minor < 4)))) {
    // Kernels before 3.4 don't handle the ASLR well and we can run out of address
    // space (http://b/13564922). Work around the issue by inhibiting further mmap() randomization.
    int old_personality = personality(0xffffffff);
    if ((old_personality & ADDR_NO_RANDOMIZE) == 0) {
      int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE);
      if (new_personality == -1) {
        LOG(WARNING) << "personality(. | ADDR_NO_RANDOMIZE) failed.";
      }
    }
  }
#endif
}
可以看出,b13564922()的作用是解决基于ARM架构的低版本内核的问题。低于3.4版本的内核不能很好处理ASLR(地址空间配置随机加载:是一种针对缓冲区溢出的安全保护技术),所以该方法是针对这一问题而提出的解决方案,不是很重要,知道就好了~

2.TimingLogger

TimingLogger类,使用它可以很方便地打印某个操作的耗时。

Dex2Oat dex2oat

创建了dex2oat对象,从这开始,进入了Dex2Oat类。

1.dex2oat.ParseArgs(argc, argv)

 void ParseArgs(int argc, char** argv) {
    original_argc = argc;
    original_argv = argv;

    InitLogging(argv);

    // Skip over argv[0].
    argv++;
    argc--;
    
    ...
    
    for (int i = 0; i < argc; i++) {
      const StringPiece option(argv[i]);
      const bool log_options = false;
      if (log_options) {
        LOG(INFO) << "dex2oat: option[" << i << "]=" << argv[i];
      }
      if (option.starts_with("--dex-file=")) {
        dex_filenames_.push_back(option.substr(strlen("--dex-file=")).data());
      } else if (option.starts_with("--dex-location=")) {
        dex_locations_.push_back(option.substr(strlen("--dex-location=")).data());
      } else if (option.starts_with("--zip-fd=")) {
        const char* zip_fd_str = option.substr(strlen("--zip-fd=")).data();
        if (!ParseInt(zip_fd_str, &zip_fd_)) {
          Usage("Failed to parse --zip-fd argument '%s' as an integer", zip_fd_str);
        }
        if (zip_fd_ < 0) {
          Usage("--zip-fd passed a negative value %d", zip_fd_);
        }
      } 
      
      ...
      
      else if (option.starts_with("--boot-image=")) {
        boot_image_filename = option.substr(strlen("--boot-image=")).data();
    }
    
    ...
 
 else if (option.starts_with("--compiler-backend=")) {
        requested_specific_compiler = true;
        StringPiece backend_str = option.substr(strlen("--compiler-backend=")).data();
        if (backend_str == "Quick") {
          compiler_kind_ = Compiler::kQuick;
        } else if (backend_str == "Optimizing") {
          compiler_kind_ = Compiler::kOptimizing;
        } else {
          Usage("Unknown compiler backend: %s", backend_str.data());
        }
      }
      
     ...
}
  • 处理命令行参数,将dex文件名文件路径等参数传入函数中
  • 参数错误将会调用UsageError中的exit(EXIT_FAILURE),成功返回表示参数成功解析。

看几个参数:
1.--dex-file=:需要转换的’dex‘文件,也可以是apk,jar中的,dex2oat会找到里面的classes.dex进行转换
2.--oat-file=:指定输出oat的文件名
3.--boot-image=:系统运行时工具类在ART下编译后的文件,他的例子是指向/system/framework/boot.art。但其实boot.art不在这个目录下,而是在/data/dalvik-cache/system@framework@boot.oat
4.--compiler-backend=(Quick|Portable):选择编译后端
5.其余参数参考dex2oat程序参数总结,当安装一个新的应用程序的时候,都会执行下面的命令对其进行编译:

/system/bin/dex2oat --zip-fd=6 --zip-location=/data/app/<Package Name>-1/base.apk --oat-fd=7 --oat-location=/data/dalvik-cache/arm/data@app@<Package Name>-1@base.apk@classes.dex --instruction-set=arm --instruction-set-features=div --runtime-arg -Xms64m --runtime-arg -Xmx512m --swap-fd=8

2.dex2oat.OpenFile

bool OpenFile() {
    bool create_file = !oat_unstripped_.empty();  // as opposed to using open file descriptor
    if (create_file) {
    ...
    }
}
  • 判断oat输出文件是否可写,并打开它。

3.dex2aot.Setup

bool Setup() {
    
    ...
    
if (boot_image_option_.empty()) {
      std::string boot_class_path = "-Xbootclasspath:";
      boot_class_path += Join(dex_filenames_, ':');
      runtime_options.push_back(std::make_pair(boot_class_path, nullptr));
      std::string boot_class_path_locations = "-Xbootclasspath-locations:";
      boot_class_path_locations += Join(dex_locations_, ':');
      runtime_options.push_back(std::make_pair(boot_class_path_locations, nullptr));
    }
    
    ...    
    
}
  • 设置编译环境,包括启动运行时和加载/打开(boot clss)引导类路径。

4.dex2oat.IsImage

bool IsImage() const {
    return image_;
  }

image_的赋值是在ParseArgs方法中:

image_ = (!image_filename_.empty());

5.CompileImage

static int CompileImage(Dex2Oat& dex2oat) {
  dex2oat.Compile();

  // Create the boot.oat.
  if (!dex2oat.CreateOatFile()) {
    dex2oat.EraseOatFile();
    return EXIT_FAILURE;
  }
 // Flush and close the boot.oat. We always expect the output file by name, and it will be
  // re-opened from the unstripped name.
  if (!dex2oat.FlushCloseOatFile()) {
    return EXIT_FAILURE;
  } 

  ...
}
基本上是对dex2oat.Compile的封装,后面都是对写文件和计时的处理,所以需要阅读dex2oat.Compile
void Compile() {
...
// Handle and ClassLoader creation needs to come after Runtime::Create
    jobject class_loader = nullptr;
    Thread* self = Thread::Current();
    if (!boot_image_option_.empty()) {
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      OpenClassPathFiles(runtime_->GetClassPathString(), dex_files_, &class_path_files_);
      ScopedObjectAccess soa(self);
      
      ...
 // Then the dex files we'll compile. Thus we'll resolve the class-path first.
      class_path_files.insert(class_path_files.end(), dex_files_.begin(), dex_files_.end());

      class_loader = class_linker->CreatePathClassLoader(self, class_path_files);
    }

    driver_ = new CompilerDriver(compiler_options_.get(),
                                 verification_results_,
                                 &method_inliner_map_,
                                 compiler_kind_,
                                 instruction_set_,
                                 instruction_set_features_.get(),
                                 image_,
                                 image_classes_.release(),
                                 compiled_classes_.release(),
                                 nullptr,
                                 thread_count_,
                                 dump_stats_,
                                 dump_passes_,
                                 dump_cfg_file_name_,
                                 compiler_phases_timings_.get(),
                                 swap_fd_,
                                 profile_file_);

    driver_->CompileAll(class_loader, dex_files_, timings_);
  }
}
1.编译之前,先对ClassLoader进行预处理。然后,就创建一个CompilerDriver对象,并调用driver的ComileAll来完成dex文件编译。
2.CompilerDriver/art/compiler/driver/compiler_driver.h做了定义,暂时不去看。

3.很有必要看下dex2oat.CreateOatFile方法:

bool CreateOatFile() {
    ...
    std::unique_ptr<OatWriter> oat_writer;
       {
       ...
       oat_writer.reset(new OatWriter(dex_files_, image_file_location_oat_checksum,
                                     image_file_location_oat_data_begin,
                                     image_patch_delta,
                                     driver_,
                                     image_writer_.get(),
                                     timings_,
                                     key_value_store_.get()));
       }
      ...
      {//driver_是CompilerDriver的对象
      if (!driver_->WriteElf(android_root_, is_host_, dex_files_, oat_writer.get(),
                             oat_file_.get())) {
        LOG(ERROR) << "Failed to write ELF file " << oat_file_->GetPath();
        return false;
      }
    }

    VLOG(compiler) << "Oat file written successfully (unstripped): " << oat_location_;

}
  • 要注意创建image还是oat文件,要确保两者之间的引用是正确的。
  • 目前,内存布局是这样的:
    clipboard.png
  • image和boot.oat的加载有几个限制条件:
    1.image要被加载到一个绝对地址,并且image中包含的对象要具有绝对指针。
    2.从image中的Method到oat中的代码要是绝对指针。
    3.从oat中的代码到image中的Method要是绝对指针。
    4.从oat中的代码到oat中的其他代码要是绝对指针。
  • 为了满足以上限制,需要做到:
    1.需要为oat文件中的所有数据准备偏移,计算oat中的数据大小和代码大小,在这个阶段,还需要将代码的便宜量放置到方法中便于image的写入器使用。
    2.需要为image文件中的对象准备偏移,并且计算image的大小。
    3.创建oat文件,一开始它是一个私有文件,但是现在是被包含在一个EFL对象中(又叫so文件)。由于我们知道image大小和oat中的数据大小和代码大小,所以这样就能够准备ELF文件的头部分,而且知道EFL文件的内存段布局,因此现在可以解析所有引用。编译器在每个CompiledMethod中提供LinkerPatch信息,使用image writer提供的布局信息和image对象位置来解析这些信息,因此准备写method的代码。
    4.创建image文件,我们需要知道oat文件的加载位置,一开始,oat文件只是内存映射,所以可以根据文件的大小预测其内容。现在它是一个EFL文件,这就需要需要检查ELF文件以了解内存段布局,包括oat头所在的位置。
    5.修复ELF文件的头,这样dlopen就会在运行时通过预设的基址上偏移Elf32_Phdr.p_vaddr值在期望的位置加载so文件。
    步骤1-3是在CreateOatFile()进行的,步骤4-5是在CreateImageFile()进行的。
  • CompileImage中的dex2oat.HandleImage()方法就调用了CreateImageFile()

6.CompileApp

static int CompileApp(Dex2Oat& dex2oat) {
  dex2oat.Compile();

  // Create the app oat.
  if (!dex2oat.CreateOatFile()) {
    dex2oat.EraseOatFile();
    return EXIT_FAILURE;
  }

  // Do not close the oat file here. We might haven gotten the output file by file descriptor,
  // which we would lose.
  if (!dex2oat.FlushOatFile()) {
    return EXIT_FAILURE;
  }

    ...
}
CompileImage类似,不同只是比CompileImage少了以下部分:
//不一定按照这个顺序
 // Flush and close the boot.oat. We always expect the output file by name, and it will be
  // re-opened from the unstripped name
if (!dex2oat.FlushCloseOatFile()) {
    return EXIT_FAILURE;
  }
// Creates the boot.art and patches the boot.oat.
  if (!dex2oat.HandleImage()) {
    return EXIT_FAILURE;
  }
// Copy unstripped to stripped location, if necessary.
  if (!dex2oat.CopyUnstrippedToStripped()) {
    return EXIT_FAILURE;
  }

wydong
40 声望5 粉丝

wyd