• [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优化就能正确进行下去。

  • 相关阅读:
    POJ 3970(最小公倍数LCM)
    06005_Jedis入门
    雷林鹏分享:C# 字符串(String)
    雷林鹏分享:C# 类(Class)
    雷林鹏分享:C# 枚举(Enum)
    雷林鹏分享:C# 运算符重载
    雷林鹏分享:C# 多态性
    雷林鹏分享:C# 命名空间(Namespace)
    雷林鹏分享:C# 接口(Interface)
    雷林鹏分享:C# 正则表达式
  • 原文地址:https://www.cnblogs.com/jacobchen/p/3599483.html
Copyright © 2020-2023  润新知