篇文章中我们介绍了 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=: specifies a .dex file to compile.
: 需要转换的 dex 文件,也可以是 apk, jar,dex2oat 会找到里面的 classes.dex 进行转换。--oat-file=: specifies the oat output destination via a filename.
: 指定输出 oat 的文件名。--boot-image=: 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-bitLSB shared object,ARM,EABI5 version1 (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
:
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
// Find base frameworks (resource packages without code).
mFrameworkInstallObserver = newAppDirObserver(
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= newFile(Environment.getRootDirectory(),"priv-app");
mPrivilegedInstallObserver= newAppDirObserver(
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.
FilesystemAppDir =new File(Environment.getRootDirectory(),"app");
mSystemInstallObserver = newAppDirObserver(
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.
FilevendorAppDir =new File("/vendor/app");
mVendorInstallObserver = newAppDirObserver(
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
:
constchar*path =env->GetStringUTFChars(pathString,NULL);
res =inotify_add_watch(fd,path,mask);
env->ReleaseStringUTFChars(pathString,path);
man 一下 inotify_add_watch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INOTIFY_ADD_WATCH(2) Linux Programmer'sManual INOTIFY_ADD_WATCH(2)
NAME
inotify_add_watch- adda watchto aninitialized inotify instance
SYNOPSIS
#include <sys/inotify.h>
int inotify_add_watch(intfd,const char*pathname,uint32_t mask);
DESCRIPTION
inotify_add_watch() adds a newwatch,or modifiesan existing watch,
for thefile whose locationis specifiedin pathname;the caller must
have readpermission forthis file. Thefd argument isa filedescrip‐
tor referring to theinotify instance whose watch listis to be modi‐
fied. The events to bemonitored forpathname are specifiedin the
mask bit-maskargument. Seeinotify(7)for adescription of the bits
that canbe set inmask.
...
原来 inotify_add_watch() 是监控目录的。
PackageManagerService.scanDirLI()
回到正题,我们从构造方法中找到了 scanDirLI(),现在来看一下 scanDirLI() 都做了什么:
3458
3459
3460
3461
3462
3463
3464
3465
for (i=0;i<files.length;i++){
File file= newFile(dir,files[i]);
if(!isPackageFilename(files[i])){
// Ignore entries which are not apk's
continue;
}
PackageParser.Packagepkg =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.PackagescannedPkg =scanPackageLI(pkg,parseFlags,scanMode
|SCAN_UPDATE_SIGNATURE,currentTime,user);
又调用了另外一个重载的 scanPackageLI()。在这个 scanPackageLI() 里面又进行了一系列的包的解析,都与我们的目标无关,直接跳到这里:
4610
4611
4612
4613
4614
4615
4616
if((scanMode&SCAN_NO_DEX)== 0){
if(performDexOptLI(pkg,forceDex,(scanMode&SCAN_DEFER_DEX)!= 0,false)
==DEX_OPT_FAILED){
mLastScanError= PackageManager.INSTALL_FAILED_DEXOPT;
returnnull;
}
}
在这里又调用 performDexOptLI() 来处理进行 dex 优化,参数就是这个 package。这里已经感觉到有点快到 dex2oat 的调用点了。我们继续看 performDexOptLI()。
PackageManagerService.performDexOptLI()
performDexOptLI 先检查 dex 是否需要优化,然后再有一些判断,忽略他,看到这里:
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
if(forceDex|| dalvik.system.DexFile.isDexOptNeeded(path)){
if(!forceDex&& defer){
if(mDeferredDexOpt== null){
mDeferredDexOpt= newHashSet<PackageParser.Package>();
}
mDeferredDexOpt.add(pkg);
returnDEX_OPT_DEFERRED;
}else {
Log.i(TAG,"Running dexopt on: "+ pkg.applicationInfo.packageName);
finalint 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= newLocalSocket();
LocalSocketAddress address= newLocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address);
mIn =mSocket.getInputStream();
mOut= mSocket.getOutputStream();
了解了这个之后,我们找一下 dexopt 看看 Installer 向 installd 发送了什么信息:
204
205
206
207
208
209
210
211
212
publicint dexopt(StringapkPath,int uid,boolean isPublic){
StringBuilder builder= newStringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
builder.append(uid);
builder.append(isPublic? " 1": " 0");
returnexecute(builder.toString());
}
其实发送了 dexopt apkpath uid 1|0
给 installd。好了,Installer 分析结束,我们转战 installd。
installd.c
installd 是 socket 通信的 server,会 accept 连接,并读取数据。从下面的两个死循环就能看出来:
554
555
556
557
558
559
560
561
562
563
564
565
566
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(;;){
unsignedshort count;
if(readx(s,&count,sizeof(count))){
之后会调用 execute 函数:
execute() 函数先把 cmd 分割然后通过 cmds 表查询函数名称:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
structcmdinfo {
constchar *name;
unsignednumargs;
int(*func)(char**arg,char reply[REPLY_MAX]);
};
structcmdinfo 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:
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
pid_tpid;
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 capabilities
struct__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);
gotofail;
}
}
开始先 drop capabilities(关于 Linux capabilities: man capabilities)。最后比较 persist_sys_dalvik_vm_lib,如果是 libart 则调用 run_dex2oat()。Good,终于有头绪了,再看 run_dex2oat():
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
staticvoid run_dex2oat(intzip_fd,int oat_fd,const char*input_file_name,
constchar*output_file_name,const char*dexopt_flags)
{
staticconst char*DEX2OAT_BIN ="/system/bin/dex2oat";
staticconst intMAX_INT_LEN =12; // '-'+10dig+'\0' -OR- 0x+8dig
charzip_fd_arg[strlen("--zip-fd=")+ MAX_INT_LEN];
charzip_location_arg[strlen("--zip-location=")+ PKG_PATH_MAX];
charoat_fd_arg[strlen("--oat-fd=")+ MAX_INT_LEN];
charoat_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
This entry was posted in Android, Technology and tagged Android, ART Runtime, dex2oat on January 10, 2014
by Mingshen Sun.
0 0