源码分析基于的是Android6.0的代码
什么是dex2oat
- dex2oat是ART虚拟机必备的一个组件,主要用来把安装的apk和动态加载的dex等文件转换成oat文件。
- 源码位置:
art\dex2oat\Dex2oat.cc
(只有一个文件) - 先看两张图(实在找不到最初的出处,感谢原始作者)
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@[email protected]
4.--compiler-backend=(Quick|Portable)
:选择编译后端
5.其余参数参考dex2oat程序参数总结,当安装一个新的应用程序的时候,都会执行下面的命令对其进行编译:
/system/bin/dex2oat --zip-fd=6 --zip-location=/data/app/-1/base.apk --oat-fd=7 --oat-location=/data/dalvik-cache/arm/data@app@[email protected]@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 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文件,要确保两者之间的引用是正确的。
- 目前,内存布局是这样的:
- 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;
}