在上一篇文章中我们介绍了 Android 4.4 新开发的运行时 ART 项目,其中的一个重要模快是 dex2oat,简单讲就是使用 LLVM 把 dex 文件编译成 oat 文件(Optimized ART?)。下面我们详细研究一下 dex2oat 的功能,以及他是如何被调用的。

一、dex2oat 简介

dex2oat 顾名思义 dex file to oat file,就是在新旧两种运行时文件的转换。他是一个可执行 ELF 文件,adb 连接手机以后也可以直接调用。dex2oat 的源码只有一个文件 /art/dex2oat/dex2oat.cc。dex2oat 有很多参数,并且实用方法打印到 logcat里面,看起来非常不便,我们直接看源码 Usage()。我挑了几个比较重要的参数看了一下,先介绍一下,调用过程中可能会用到。

dex2oat 用法

  • --dex-file=<dex-file>: specifies a .dex file to compile.: 需要转换的 dex 文件,也可以是 apk, jar,dex2oat 会找到里面的 classes.dex 进行转换。
  • --oat-file=<file.oat>: specifies the oat output destination via a filename.: 指定输出 oat 的文件名。
  • --boot-image=<file.art>: provide the image file for the boot class path.: 系统运行时工具类在 ART 下编译后的文件,他的例子是指向/system/framework/boot.art。但其实 boot.art 不在这个目录下,而是在/data/dalvik-cache/system@framework@boot.oat。后面还会详细介绍这个 boot 文件。
  • --compiler-backend=(Quick|QuickGBC|Portable): select compiler backend": dex2oat 好像只处理了 Quick 和 Portable 两种编译 backend,暂时还不理解有什么区别,待以后继续研究。

其他参数大多都能从他的描述中知道用途,等以后用到的时候再详细看。

oat 文件在哪里?

我们知道选择 ART 作为运行时后,需要重启手机。然后系统会使用 dex2oat 把所有 App 编译成 oat 文件。那么 oat 都存在哪里?我怎么找不到?通过查看代码和开机的 log,发现 oat 文件原来在 /data/dalvik-cache/*@classes.dex。竟然和 odex 文件名字一样!检验一下文件格式:

$ file system@app@Email.apk@classes.dex
system@app@Email.apk@classes.dex: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, stripped

果然是 ELF 文件。

二、调用过程分析

PackageManagerService 是负责 apk 包管理的服务,包括安装,检查,删除,更新等等所有与 apk 相关的服务。我在源码中研究了一下 dex2oat 详细的调用过程,在下面介绍过程的时候我打算从 PackageManagerService 入手,经过一系列的调用过程才真正执行 dex2oat。

PackageManagerService (PMS) 我们知道 PMS 服务启动之后会监控某些目录(/data/data/ 等)。如果目录多了某个文件,PMS 会调用一些方法对文件进行处理。扫描监控目录的方法是:scanDirLI(),在 PackageManagerService 的构造方法中调用,详见代码 /frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

// Find base frameworks (resource packages without code).
mFrameworkInstallObserver = new AppDirObserver(frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
mFrameworkInstallObserver.startWatching();
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR,scanMode | SCAN_NO_DEX, 0);// Collected privileged system packages.
File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
mPrivilegedInstallObserver = new AppDirObserver(privilegedAppDir.getPath(), OBSERVER_EVENTS, true, true);
mPrivilegedInstallObserver.startWatching();scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0);// Collect ordinary system packages.
File systemAppDir = new File(Environment.getRootDirectory(), "app");
mSystemInstallObserver = new AppDirObserver(systemAppDir.getPath(), OBSERVER_EVENTS, true, false);
mSystemInstallObserver.startWatching();
scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
mVendorInstallObserver = new AppDirObserver(vendorAppDir.getPath(), OBSERVER_EVENTS, true, false);
mVendorInstallObserver.startWatching();
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);

从源码中可以看到,有四个地方调用 scanDirLI()。再调用之前有一个 AppDirObserver 类引起了我的注意,看一下是怎么对目录进行监控的。依然是在 PackageManagerService 中找到了 AppDirObserver 继承于 FileObserver。FileObserver 有一个 native 方法 startWatching 找到了他的实现 /frameworks/base/core/java/android/os/FileObserver.java

const char* path = env->GetStringUTFChars(pathString, NULL);
res = inotify_add_watch(fd, path, mask);
env->ReleaseStringUTFChars(pathString, path);

man 一下 inotify_add_watch:

INOTIFY_ADD_WATCH(2)       Linux Programmer's Manual      INOTIFY_ADD_WATCH(2)NAMEinotify_add_watch - add a watch to an initialized inotify instanceSYNOPSIS#include <sys/inotify.h>int inotify_add_watch(int fd, const char *pathname, uint32_t mask);DESCRIPTIONinotify_add_watch()  adds  a  new watch, or modifies an existing watch,for the file whose location is specified in pathname; the  caller  musthave read permission for this file.  The fd argument is a file descrip‐tor referring to the inotify instance whose watch list is to  be  modi‐fied.   The  events  to  be monitored for pathname are specified in themask bit-mask argument.  See inotify(7) for a description of  the  bitsthat can be set in mask.
...

原来 inotify_add_watch() 是监控目录的。

PackageManagerService.scanDirLI()

回到正题,我们从构造方法中找到了 scanDirLI(),现在来看一下 scanDirLI() 都做了什么:

for (i=0; i<files.length; i++) {File file = new File(dir, files[i]);if (!isPackageFilename(files[i])) {// Ignore entries which are not apk'scontinue;}PackageParser.Package pkg = scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null);

scanDirLI() 便利所有目录下的 apk 文件并调用 scanPackageLI(),我们继续。

PackageManagerService.scanPackageLI()

scanPackageLI() 先对文件的设置进行读取,还包含其他处理。我们跳过直接看

// Note that we invoke the following method only if we are about to unpack an application
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode| SCAN_UPDATE_SIGNATURE, currentTime, user);

又调用了另外一个重载的 scanPackageLI()。在这个 scanPackageLI() 里面又进行了一系列的包的解析,都与我们的目标无关,直接跳到这里:

if ((scanMode&SCAN_NO_DEX) == 0) {if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)== DEX_OPT_FAILED) {mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;return null;}
}

在这里又调用 performDexOptLI() 来处理进行 dex 优化,参数就是这个 package。这里已经感觉到有点快到 dex2oat 的调用点了。我们继续看 performDexOptLI()。

PackageManagerService.performDexOptLI()

performDexOptLI 先检查 dex 是否需要优化,然后再有一些判断,忽略他,看到这里:

/* /frameworks/base/services/java/com/android/server/pm/PackageManagerService.java */
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {if (!forceDex && defer) {if (mDeferredDexOpt == null) {mDeferredDexOpt = new HashSet<PackageParser.Package>();}mDeferredDexOpt.add(pkg);return DEX_OPT_DEFERRED;} else {Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.   uid);ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));pkg.mDidDexOpt = true;performed = true;}
}

最后 mInstaller.dexopt 调用的优化 dex 的函数,Google 在这里把 dex2oat 和 dex2odex 统称为 dexopt。mInstaller 是 Installer 类。下面我们详细了解一下 Installer 的运行机制。

Installer

从 Installer 的代码中我们可以看到 connect(), disconnect(), readBytes(), writeCommand() 等方法。在 connect() 方法中可以看到,Installer 就是与 installd daemon 进行通信的辅助类。代码在这里:

mSocket = new LocalSocket();LocalSocketAddress address = new LocalSocketAddress("installd",LocalSocketAddress.Namespace.RESERVED);mSocket.connect(address);mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();

了解了这个之后,我们找一下 dexopt 看看 Installer 向 installd 发送了什么信息:

public int dexopt(String apkPath, int uid, boolean isPublic) {StringBuilder builder = new StringBuilder("dexopt");builder.append(' ');builder.append(apkPath);builder.append(' ');builder.append(uid);builder.append(isPublic ? " 1" : " 0");return execute(builder.toString());
}

其实发送了 dexopt apkpath uid 1|0 给 installd。好了,Installer 分析结束,我们转战 installd。

installd.c

installd 是 socket 通信的 server,会 accept 连接,并读取数据。从下面的两个死循环就能看出来:

for (;;) {alen = sizeof(addr);s = accept(lsocket, &addr, &alen);if (s < 0) {ALOGE("Accept failed: %s\n", strerror(errno));continue;}fcntl(s, F_SETFD, FD_CLOEXEC);ALOGI("new connection\n");for (;;) {unsigned short count;if (readx(s, &count, sizeof(count))) {

之后会调用 execute 函数:

if (execute(s, buf)) break;

execute() 函数先把 cmd 分割然后通过 cmds 表查询函数名称:

struct cmdinfo {const char *name;unsigned numargs;int (*func)(char **arg, char reply[REPLY_MAX]);
};struct cmdinfo cmds[] = {{ "ping",                 0, do_ping },{ "install",              4, do_install },{ "dexopt",               3, do_dexopt },{ "movedex",              2, do_move_dex },{ "rmdex",                1, do_rm_dex },{ "remove",               2, do_remove },{ "rename",               2, do_rename },{ "fixuid",               3, do_fixuid },{ "freecache",            1, do_free_cache },{ "rmcache",              2, do_rm_cache },{ "getsize",              6, do_get_size },{ "rmuserdata",           2, do_rm_user_data },{ "movefiles",            0, do_movefiles },{ "linklib",              3, do_linklib },{ "mkuserdata",           3, do_mk_user_data },{ "rmuser",               1, do_rm_user },
};

dexopt 对应的是 do_dexopt() 函数,do_dexopt() 又调用了 commands.c 里面的 dexopt() 函数。感觉离我们的目标不远了,继续看 commands.c。

commands.c

在 commands.c 中的 dexopt() 函数,我们看到此函数从 property 中获得 dalvik.vm.dexopt-flags 和 persist.sys.dalvik.vm.lib:

/* platform-specific flags affecting optimization and verification */
property_get("dalvik.vm.dexopt-flags", dexopt_flags, "");
ALOGV("dalvik.vm.dexopt_flags=%s\n", dexopt_flags);/* The command to run depend ones the value of persist.sys.dalvik.vm.lib */
property_get("persist.sys.dalvik.vm.lib", persist_sys_dalvik_vm_lib, "libdvm.so");

dalvik.vm.dexopt-flags 暂时不知道是什么意思,persist.sys.dalvik.vm.lib 就是当前系统使用的运行时默认是 Dalvik VM,如果选择了 ART,应该就是 libart.so 了。

之后函数通过检查是否有 odex 文件来判断是否已经进行过 dexopt。然后进行一系列检查 chmod 和 chown。最后函数 fork 了一个子进程处理 dexopt:

pid_t pid;
pid = fork();
if (pid == 0) {/* child -- drop privileges before continuing */if (setgid(uid) != 0) {ALOGE("setgid(%d) failed in installd during dexopt\n", uid);exit(64);}if (setuid(uid) != 0) {ALOGE("setuid(%d) failed in installd during dexopt\n", uid);exit(65);}// drop capabilitiesstruct __user_cap_header_struct capheader;struct __user_cap_data_struct capdata[2];memset(&capheader, 0, sizeof(capheader));memset(&capdata, 0, sizeof(capdata));capheader.version = _LINUX_CAPABILITY_VERSION_3;if (capset(&capheader, &capdata[0]) < 0) {ALOGE("capset failed: %s\n", strerror(errno));exit(66);}if (flock(out_fd, LOCK_EX | LOCK_NB) != 0) {ALOGE("flock(%s) failed: %s\n", out_path, strerror(errno));exit(67);}if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);} else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);} else {exit(69);   /* Unexpected persist.sys.dalvik.vm.lib value */}exit(68);   /* only get here on exec failure */
} else {res = wait_dexopt(pid, apk_path);if (res != 0) {ALOGE("dexopt in='%s' out='%s' res=%d\n", apk_path, out_path, res);goto fail;}
}

开始先 drop capabilities(关于 Linux capabilities: man capabilities)。最后比较 persist_sys_dalvik_vm_lib,如果是 libart 则调用 run_dex2oat()。Good,终于有头绪了,再看 run_dex2oat():

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+8digchar 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));
}

终于找到了,这就是调用 dex2oat 的地方,写的非常清楚。–zip-fd 是 apk 的 fd,–oat-fd 是输出 oat 的 fd。最终使用 execl() 执行 /system/bin/dex2oat 程序对 apk 进行编译。

三、总结

dex2oat 对所有 apk 进行编译并保存在 dalvik-cache 目录里。PMS 会持续扫描安装目录,如果有新的 App 安装则马上调用 dex2oat 进行编译。

最后,之前我们提到的 boot.art 我在 logcat 中找到了他的来源:

/system/bin/dex2oat --image=/data/dalvik-cache/system@framework@boot.art \
--runtime-arg -Xms64m --runtime-arg -Xmx64m \
--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/framework2.jar \
--dex-file=/system/framework/telephony-common.jar \
--dex-file=/system/framework/voip-common.jar \
--dex-file=/system/framework/mms-common.jar \
--dex-file=/system/framework/android.policy.jar \
--dex-file=/system/framework/services.jar \
--dex-file=/system/framework/apache-xml.jar \
--dex-file=/system/framework/webviewchromium.jar \
--oat-file=/data/dalvik-cache/system@framework@boot.oat --base=0x60000000 \
--image-classes-zip=/system/framework/framework.jar \
--image-classes=preloaded-classes

似乎是把所有 framework 的 jar 编译成 boot.art,在 dex2oat 的时候使用。还是有很多不理解的地方,相信在以后的分析中应该有更深入的理解。

附:相关代码链接

  • PackageManagerService constructor: http://androidxref.com/4.4_r1/xref/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java#1062
  • scanDirLI(): http://androidxref.com/4.4_r1/xref/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java#3445
  • scanPackageLI():http://androidxref.com/4.4_r1/xref/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java#4103
  • performDexOptLI(): http://androidxref.com/4.4_r1/xref/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java#3860
  • Installer.dexopt(): http://androidxref.com/4.4_r1/xref/frameworks/base/services/java/com/android/server/pm/Installer.java#204
  • do_dexopt(): http://androidxref.com/4.4_r1/xref/frameworks/native/cmds/installd/installd.c#37
  • dexopt(): http://androidxref.com/4.4_r1/xref/frameworks/native/cmds/installd/commands.c#653
  • run_dex2oat(): http://androidxref.com/4.4_r1/xref/frameworks/native/cmds/installd/commands.c#run_dex2oat

剖析 Android ART Runtime (2) – dex2oat相关推荐

  1. android art源码分析,Android ART机制分析

    本文章由Jack_Jia编写,转载请注明出处.文章链接:外链网址已屏蔽 作者:Jack_Jia邮箱: 2013年度"博客之星"投票火热进行中,欢迎投票支持我: 一.Android系 ...

  2. Android ART dex2oat 加载加速浅析

    前言 手机淘宝插件化框架Atlas在ART上首次启动的时候,会通过禁用dex2oat来达到插件迅速启动的目的.之后后台进行dex2oat,下次启动如果dex2oat完成了则启用dex2oat,如果没有 ...

  3. 【Android 逆向】ART 脱壳 ( 修改 /art/runtime/dex_file.cc#OpenCommon 系统源码进行脱壳 )

    文章目录 一.要修改的源码 /art/runtime/dex_file.cc#OpenCommon 二.修改 /art/runtime/dex_file.cc#OpenCommon 函数源码进行脱壳 ...

  4. 《深入理解Android 5 源代码》——第1章,第1.2节剖析Android系统架构

    本节书摘来自异步社区<深入理解Android 5 源代码>一书中的第1章,第1.2节剖析Android系统架构,作者 李骏,更多章节内容可以访问云栖社区"异步社区"公众 ...

  5. android art 远程控制,也来看看Android的ART运行时

    之前因为需要,研究了一下ART的相关源码,也做了一些记录与总结,现在重新整理了一下与大家共同讨论和交流一下. 0x00 概述 ART是Android平台上的新一代运行时,用来代替dalvik.它主要采 ...

  6. android ART学习

    android ART ART(Android Runtime) 是Android5.0以后所使用的一种新的虚拟机.ART采用Ahead-of-time(AOT:Android系统自带的dex2oat ...

  7. Android ART简介

    Android ART简介 android ART(android runtime)是anroid 4.4以后用来替换Davlik虚拟机的一种运行环境,它是一个android操作系统使用的虚拟机,它将 ...

  8. Android ART 分析

    对Android ART的分析,主要包括ART Runtime启动过程以及dex2oat的分析. 由于代码量较多,忽略了很多细节,所以分析过程会存在错误:ART Runtime采用单例模式,启动过程中 ...

  9. Android art模式解析

    Android art模式解析 本文主要针对android系统art模式下面从安装apk到运行apk的一个过程,主要有一下几个方面: Art虚拟机介绍 安装时dex文件转化为oat文件 oat文件对应 ...

最新文章

  1. 2020卫星参数表大全_王者荣耀比较秀的名字 2020年比较骚气比较浪的王者荣耀名字大全...
  2. mysql 判度数据库存在,问题出在什么地方啊?
  3. sed和awk的常用实例
  4. LeetCode Battleships in a Board
  5. 怎么解决64位Access与32位不能同时安装的问题
  6. oracle12c考试内容,12c ocp考试内容
  7. DevExpress控件使用经验总结
  8. TCP相关的面试内容整理
  9. #define c# 报错_c语言中#define的用法
  10. 使用Windows版Redis
  11. 写个脚本快速启动前后端
  12. 剑指offer面试题[9-2]-变态跳台阶
  13. Normalization的方法
  14. 机器学习入门之——动手演示线性模型无法表示的XOR问题
  15. 微信全球MBA创新大赛Roadshow最终站火爆中欧
  16. 在线ERD工具DrawERD
  17. 2014年6月计算机二级c语言答案,2014年计算机二级C语言真题及答案(4)
  18. 极光推送 简书android,极光推送 (具体步骤,指导操作,推送成功)
  19. TPM分析笔记(二)TPM2.0 规范文档
  20. 今天那个服务器有无限火力,无限火力即将登陆,测试服已出,这次的无限火力有什么不一样?...

热门文章

  1. 欧拉角的概念理解和欧拉角旋转矩阵推导
  2. Git 2.38发布,引入巨型仓库管理工具Scalar
  3. openFOAM中的Scalar
  4. 《单核工作法图解》阅读摘要
  5. 【数学】HDU 5761 Rower Bo
  6. 深入机器学习系列之自然语言处理
  7. JAVA的内存回收机制(快速入门版)
  8. 编写函数:笛卡尔坐标系(Append Code)
  9. 什么是VGA、QVGA、CIF、QCIF。。。?
  10. 跨境贸易PayPal收款,个人美金额度不够了,如何提现美金?