剖析 Android ART Runtime (2) – dex2oat
在上一篇文章中我们介绍了 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相关推荐
- android art源码分析,Android ART机制分析
本文章由Jack_Jia编写,转载请注明出处.文章链接:外链网址已屏蔽 作者:Jack_Jia邮箱: 2013年度"博客之星"投票火热进行中,欢迎投票支持我: 一.Android系 ...
- Android ART dex2oat 加载加速浅析
前言 手机淘宝插件化框架Atlas在ART上首次启动的时候,会通过禁用dex2oat来达到插件迅速启动的目的.之后后台进行dex2oat,下次启动如果dex2oat完成了则启用dex2oat,如果没有 ...
- 【Android 逆向】ART 脱壳 ( 修改 /art/runtime/dex_file.cc#OpenCommon 系统源码进行脱壳 )
文章目录 一.要修改的源码 /art/runtime/dex_file.cc#OpenCommon 二.修改 /art/runtime/dex_file.cc#OpenCommon 函数源码进行脱壳 ...
- 《深入理解Android 5 源代码》——第1章,第1.2节剖析Android系统架构
本节书摘来自异步社区<深入理解Android 5 源代码>一书中的第1章,第1.2节剖析Android系统架构,作者 李骏,更多章节内容可以访问云栖社区"异步社区"公众 ...
- android art 远程控制,也来看看Android的ART运行时
之前因为需要,研究了一下ART的相关源码,也做了一些记录与总结,现在重新整理了一下与大家共同讨论和交流一下. 0x00 概述 ART是Android平台上的新一代运行时,用来代替dalvik.它主要采 ...
- android ART学习
android ART ART(Android Runtime) 是Android5.0以后所使用的一种新的虚拟机.ART采用Ahead-of-time(AOT:Android系统自带的dex2oat ...
- Android ART简介
Android ART简介 android ART(android runtime)是anroid 4.4以后用来替换Davlik虚拟机的一种运行环境,它是一个android操作系统使用的虚拟机,它将 ...
- Android ART 分析
对Android ART的分析,主要包括ART Runtime启动过程以及dex2oat的分析. 由于代码量较多,忽略了很多细节,所以分析过程会存在错误:ART Runtime采用单例模式,启动过程中 ...
- Android art模式解析
Android art模式解析 本文主要针对android系统art模式下面从安装apk到运行apk的一个过程,主要有一下几个方面: Art虚拟机介绍 安装时dex文件转化为oat文件 oat文件对应 ...
最新文章
- 2020卫星参数表大全_王者荣耀比较秀的名字 2020年比较骚气比较浪的王者荣耀名字大全...
- mysql 判度数据库存在,问题出在什么地方啊?
- sed和awk的常用实例
- LeetCode Battleships in a Board
- 怎么解决64位Access与32位不能同时安装的问题
- oracle12c考试内容,12c ocp考试内容
- DevExpress控件使用经验总结
- TCP相关的面试内容整理
- #define c# 报错_c语言中#define的用法
- 使用Windows版Redis
- 写个脚本快速启动前后端
- 剑指offer面试题[9-2]-变态跳台阶
- Normalization的方法
- 机器学习入门之——动手演示线性模型无法表示的XOR问题
- 微信全球MBA创新大赛Roadshow最终站火爆中欧
- 在线ERD工具DrawERD
- 2014年6月计算机二级c语言答案,2014年计算机二级C语言真题及答案(4)
- 极光推送 简书android,极光推送 (具体步骤,指导操作,推送成功)
- TPM分析笔记(二)TPM2.0 规范文档
- 今天那个服务器有无限火力,无限火力即将登陆,测试服已出,这次的无限火力有什么不一样?...