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

2015-05-11 15:14:39 1262浏览


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

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

    由于OAT文件本质上是一个ELF文件,因此在最外层它具有一般ELF文件的结构,例如它有标准的ELF文件头以及通过段(Section)来描述文件内容。

     作为Android私有的一种ELF文件,OAT文件包含有两个特殊的段oatdataoatexec,前者包含有用来生成本地机器指令的dex文件内容,后者包含有生成的本地机器指令,它们之间的关系通过储存在oatdata段前面的oat头部描述。此外,在OAT文件的dynamic段,导出了三个符号oatdataoatexecoatlastword,它们的值就是用来界定oatdata段和oatexec段的起止位置的。其中,[oatdata, oatexec - 4]描述的是oatdata段的起止位置,而[oatexec, oatlastword]描述的是oatlastword的起止位置。要完全理解OAT的文件格式,除了要理解本文即将要分析的OAT加载过程之外,还需要掌握接下来文章分析的类和方法查找过程。

    在分析OAT文件的加载过程之前,我们需要简单介绍一下OAT是如何产生的。如前面Android ART运行时无缝替换Dalvik虚拟机的过程分析一文所示,APK在安装的过程中,会通过dex2oat工具生成一个OAT文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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+'' -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_fdoat_fd都是打开文件描述符,指向的分别是正在安装的APK文件和要生成的OAT文件。OAT文件的生成过程主要就是涉及到将包含在APK里面的classes.dex文件的DEX字节码翻译成本地机器指令。这相当于是编写一个输入文件为DEX、输出文件为OAT的编译器。这个编译器是基于LLVM开发的。编译器的工作原理比较高大上,所幸的是它不会影响到我们接下来的分析,因此我们就略过DEX字节码翻译成本地机器指令的过程,假设它很愉快地完成了。

    APK安装过程中生成的OAT文件的输入只有一个DEX文件,也就是来自于打包在要安装的APK文件里面的classex.dex文件。实际上,一个OAT文件是可以由若干个DEX生成的。这意味着在生成的OAT文件的oatdata段中,包含有多个DEX文件。那么,在什么情况下,会生成包含多个DEX文件的OAT文件呢?

从前面Android ART运行时无缝替换Dalvik虚拟机的过程分析一文可以知道,当我们选择了ART运行时时,Zygote进程在启动的过程中,会调用libart.so里面的函数JNI_CreateJavaVM来创建一个ART虚拟机。函数JNI_CreateJavaVM的实现如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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,它的实现如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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的实现如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

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类的静态成员函数CreateART虚拟机的启动参数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来创建一个ClassLinker对象。否则的话,上述ClassLinker对象就要通过ClassLinker类的另外一个静态成员函数CreateFromCompiler来创建。创建出来的ClassLinker对象是后面ART虚拟机加载加载Java类时要用到的。

    后面我们分析ART虚拟机的垃圾收集机制时会看到,ART虚拟机的堆包含有三个连续空间和一个不连续空间。三个连续空间分别用来分配不同的对象。当第一个连续空间不是Image空间时,就表明当前进程不是Zygote进程,而是安装应用程序时启动的一个dex2oat进程。安装应用程序时启动的dex2oat进程也会在内部创建一个ART虚拟机,不过这个ART虚拟机是用来将DEX字节码编译成本地机器指令的,而Zygote进程创建的ART虚拟机是用来运行应用程序的。

接下来我们主要分析ParsedOptions类的静态成员函数CreateART虚拟机堆Heap的构造函数,以便可以了解ART虚拟机的启动参数解析过程和ART虚拟机的堆创建过程。

ParsedOptions类的静态成员函数Create的实现如下所示:


Runtime::ParsedOptions* Runtime::ParsedOptions::Create(const Options& options, bool ignore_unrecognized) {

UniquePtr<ParsedOptions> parsed(new ParsedOptions());

const char* boot_class_path_string = getenv("BOOTCLASSPATH");

if (boot_class_path_string != NULL) {

parsed->boot_class_path_string_ = boot_class_path_string;

}

......

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();

} else if (option == "bootclasspath") {

parsed->boot_class_path_

= reinterpret_cast<const std::vector<const DexFile*>*>(options[i].second);

} else if (StartsWith(option, "-Ximage:")) {

parsed->image_ = option.substr(strlen("-Ximage:")).data();

} else if (......) {

......

} else if (option == "compiler") {

parsed->is_compiler_ = true;

} else {

...... 


标签:

热门专区

暂无热门资讯

课程推荐

微信
微博
15311698296

全国免费咨询热线

邮箱:codingke@1000phone.com

官方群:148715490

北京千锋互联科技有限公司版权所有   北京市海淀区宝盛北里西区28号中关村智诚科创大厦4层
京ICP备12003911号-6   Copyright © 2013 - 2019

京公网安备 11010802030908号