[Android]Dalvik的BOOTCLASSPATH和dexopt流程

BOOTCLASSPATH简介
1.BOOTCLASSPATH是Android Linux的一个环境变量,可以在adb shell下用$BOOTCLASSPATH看到。
2.BOOTCLASSPATH于/init.rc文件中export,如果没有找到的话,可以在init.rc中import的文件里找到(如import /init.environ.rc)。
3.init.rc文件存在于boot.img的ramdisk映像中。如果仅仅是修改/init.rc文件,重启后会被ramdisk恢复,所以直接修改是没有效果的。
4.boot.img是一种特殊的Android定制格式,由boot header,kernel,ramdisk以及second stage loader(可选)组成,详见android/system/core/mkbootimg/bootimg.h。

 

boot.img空间结构:

** +-----------------+
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages
** +-----------------+
** | ramdisk         | m pages
** +-----------------+
** | second stage    | o pages
** +-----------------+


典型的ramdisk文件结构:
./init.trout.rc
./default.prop
./proc
./dev
./init.rc
./init
./sys
./init.goldfish.rc
./sbin
./sbin/adbd
./system
./data

 

BOOTCLASSPATH的作用
以Android4.4手机的BOOTCLASSPATH为例:
export BOOTCLASSPATH /system/framework/core.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar...
当kernel启动时1号进程init解析init.rc,将/system/framework下的jar包路径export出来。
Dalvik虚拟机在初始化过程中,会读取环境变量BOOTCLASSPATH,用于之后的类加载和优化。

 

Dalvik虚拟机的启动和dexopt流程
Dalvik虚拟机的启动过程分析 一文可以知道,Zygote会在启动后创建Dalvik虚拟机实例,并进行初始化。

那我们就接着Dalvik虚拟机初始化后开始探究它是如何通过BOOTCLASSPATH来进行dex优化的:

1.1. VM initialization

android/dalvik/vm/Init.cpp

std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
{
	...
	ALOGV("VM init args (%d):", argc);
	...

	setCommandLineDefaults(); // ---> 读取BOOTCLASSPATH
	...
	if (!dvmClassStartup()) { // ---> 初始化bootstrap class loader
		return "dvmClassStartup failed";
	}
}

 1.2. 读取BOOTCLASSPATH

static void setCommandLineDefaults()
{
	const char* envStr = getenv("CLASSPATH");
	if (envStr != NULL) {
		gDvm.classPathStr = strdup(envStr);
	} else {
		gDvm.classPathStr = strdup(".");
	}
	envStr = getenv("BOOTCLASSPATH"); // 读取到BOOTCLASSPATH环境变量
if (envStr != NULL) { gDvm.bootClassPathStr = strdup(envStr); } else { gDvm.bootClassPathStr = strdup("."); } ... }

 就这样,BOOTCLASSPATH的值被保存到gDvm.bootClassPathStr中。

 

2.1. 初始化bootstrap class loader

android/dalvik/vm/oo/Class.cpp

bool dvmClassStartup()
{
	...
	
	/*
	* Process the bootstrap class path. This means opening the specified
	* DEX or Jar files and possibly running them through the optimizer.
	*/
	assert(gDvm.bootClassPath == NULL);
	processClassPath(gDvm.bootClassPathStr, true); // 下一步

	if (gDvm.bootClassPath == NULL)
		return false;
}

 2.2. 将路径、Zip文件和Dex文件的list转换到ClassPathEntry结构体当中

static ClassPathEntry* processClassPath(const char* pathStr, bool isBootstrap)
{
	ClassPathEntry* cpe = NULL;
	...
	    if (!prepareCpe(&tmp, isBootstrap)) {}
}

 2.3. 根据cpe打开文件

static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)
{
    ...
 
    if ((strcmp(suffix, "jar") == 0) || (strcmp(suffix, "zip") == 0) ||
    (strcmp(suffix, "apk") == 0)) {
        JarFile* pJarFile = NULL;
        /* 打开jar包,找到class.dex或jar包旁边的.odex文件 */
        if (dvmJarFileOpen(cpe->fileName, NULL, &pJarFile, isBootstrap) == 0) {
            cpe->kind = kCpeJar;
            cpe->ptr = pJarFile;
            return true;
        }
    } else if (strcmp(suffix, "dex") == 0) {
        RawDexFile* pRawDexFile = NULL;
        /* 与dvmJarFileOpen函数作用类似,是由它复制过来重构的 */
        if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0) {
            cpe->kind = kCpeDex;
            cpe->ptr = pRawDexFile;
            return true;
        }
    } else {
        ALOGE("Unknown type suffix '%s'", suffix);
    }
    ...
}

 

3. 打开jar包,找到class.dex或jar包旁边的.odex文件

android/dalvik/vm/JarFile.cpp

int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
    JarFile** ppJarFile, bool isBootstrap)
{
    ...
 
    /* Even if we're not going to look at the archive, we need to
     * open it so we can stuff it into ppJarFile.
     */
    if (dexZipOpenArchive(fileName, &archive) != 0)
        goto bail;
    archiveOpen = true;
 
    /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
     */
    dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
 
    /* First, look for a ".odex" alongside the jar file.  It will
     * have the same name/path except for the extension.
     */
    fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
    if (fd >= 0) {
		ALOGV("Using alternate file (odex) for %s ...", fileName);
		/* 读、验证header和dependencies */
		if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
            ALOGE("%s odex has stale dependencies", fileName);
            free(cachedName);
            cachedName = NULL;
            close(fd);
            fd = -1;
            goto tryArchive;
        } else {
            ALOGV("%s odex has good dependencies", fileName);
            //TODO: make sure that the .odex actually corresponds
            //      to the classes.dex inside the archive (if present).
            //      For typical use there will be no classes.dex.
        }
    } else {
        ZipEntry entry;
 
tryArchive:
        /*
         * Pre-created .odex absent or stale.  Look inside the jar for a
         * "classes.dex".
         */
        ...
}

 

 4.读、验证opt的header,读、验证dependencies

android/dalvik/vm/analysis/DexPrepare.cpp

bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,
u4 crc, bool expectVerify, bool expectOpt)
{
     ...
    /*
     * Verify dependencies on other cached DEX files.  It must match
     * exactly with what is currently defined in the bootclasspath.
     */
    ClassPathEntry* cpe;
    u4 numDeps;

    numDeps = read4LE(&ptr);
    ALOGV("+++ DexOpt: numDeps = %d", numDeps);
    for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
        const char* cacheFileName =
            dvmPathToAbsolutePortion(getCacheFileName(cpe));
        assert(cacheFileName != NULL); /* guaranteed by Class.c */

        const u1* signature = getSignature(cpe);
        size_t len = strlen(cacheFileName) +1;
        u4 storedStrLen;

        if (numDeps == 0) {
            /* more entries in bootclasspath than in deps list */
            ALOGI("DexOpt: not all deps represented");
            goto bail;
        }

        storedStrLen = read4LE(&ptr);
        if (len != storedStrLen ||
            strcmp(cacheFileName, (const char*) ptr) != 0)
        {
            ALOGI("DexOpt: mismatch dep name: '%s' vs. '%s'",
                cacheFileName, ptr);
            goto bail;
        }

        ptr += storedStrLen;

        if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {
            ALOGI("DexOpt: mismatch dep signature for '%s'", cacheFileName);
            goto bail;
        }
        ptr += kSHA1DigestLen;

        ALOGV("DexOpt: dep match on '%s'", cacheFileName);

        numDeps--;
    }

    if (numDeps != 0) {
        /* more entries in deps list than in classpath */
        ALOGI("DexOpt: Some deps went away");
        goto bail;
    }
	...
}

 

实际应用
打通了Dalvik dexopt的这个流程,那这到底又有什么用呢?
让我们看看实际开发过程中的手机升级binary后无法boot到Home界面的log:

 1 AndroidRuntime >>>>>> AndroidRuntime START com.android.internal.os.ZygoteInit <<<<<<
 2 AndroidRuntime CheckJNI is ON
 3 dalvikvm DexOpt: Some deps went away
 4 dalvikvm /system/framework/core-junit.jar odex has stale dependencies
 5 dalvikvm DexOpt: --- BEGIN 'core-junit.jar' (bootstrap=1) ---
 6 dalvikvm DexOpt: load 42ms, verify+opt 25ms, 143956 bytes
 7 dalvikvm DexOpt: --- END 'core-junit.jar' (success) ---
 8 dalvikvm DEX prep '/system/framework/core-junit.jar': unzip in 1ms, rewrite 126ms
 9 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
10 dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies
11 dalvikvm DexOpt: --- BEGIN 'bouncycastle.jar' (bootstrap=1) ---
12 dalvikvm DexOpt: Some deps went away
13 dalvikvm /system/framework/core-junit.jar odex has stale dependencies
14 dalvikvm DexOpt: load 33ms, verify+opt 350ms, 681812 bytes
15 dalvikvm DexOpt: --- END 'bouncycastle.jar' (success) ---
16 dalvikvm DEX prep '/system/framework/bouncycastle.jar': unzip in 57ms, rewrite 548ms
17 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
18 dalvikvm /system/framework/ext.jar odex has stale dependencies
19 dalvikvm DexOpt: --- BEGIN 'ext.jar' (bootstrap=1) ---
20 dalvikvm DexOpt: Some deps went away
21 dalvikvm /system/framework/core-junit.jar odex has stale dependencies
22 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
23 dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies

根据前面的流程,结合log我们就可以分析出,DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'是错误所在,是由于data/dalvik-cache/下的dex cache文件和system/framework/下的jar文件验证依赖关系时候对应不上。

从函数dvmCheckOptHeaderAndDependencies()可以得知,BOOTCLASSPATH和cache必须是完全一致的
尝试删除所有cache文件,重启还是不行。那么应该想到BOOTCLASSPATH和实际的system/framework/的jar包不一致,才会导致和其生成的cache不一致。
对比一下果然不一致,issue trouble-shooted.

解决方法:把对应boot.img也烧进去,这样BOOTCLASSPATH就能更新一致,dex优化就能正确进行下去。

posted on 2014-03-13 22:29  JacobChen2012  阅读(9095)  评论(0编辑  收藏  举报

导航