Android dex加载过程分析

来源:互联网 发布:网络语言ph是什么意思 编辑:程序博客网 时间:2024/06/05 11:33

啊啊啊啊啊啊啊,开始吧。

ClassLoader有两种,PathClassLoader和DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {     public DexClassLoader(String dexPath, String optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    }}
public class PathClassLoader extends BaseDexClassLoader {     public PathClassLoader(String dexPath, ClassLoader parent) {        super(dexPath, null, null, parent);    }     public PathClassLoader(String dexPath, String libraryPath,            ClassLoader parent) {        super(dexPath, null, libraryPath, parent);    }}
源码就这么点,我也是惊了个呆

不同之处就在于super()函数的第二个参数

上次说到安装阶段已经生成了一个优化后的dex,所以PathClassLoader当中就是null,DexClassLoader则需要自行生成一个优化文件。

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(parent);        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }
BaseDexClassLoader的构造函数

    public DexPathList(ClassLoader definingContext, String dexPath,            String libraryPath, File optimizedDirectory) {        if (definingContext == null) {            throw new NullPointerException("definingContext == null");        }        if (dexPath == null) {            throw new NullPointerException("dexPath == null");        }        if (optimizedDirectory != null) {            if (!optimizedDirectory.exists())  {                throw new IllegalArgumentException(                        "optimizedDirectory doesn't exist: "                        + optimizedDirectory);            }            if (!(optimizedDirectory.canRead()                            && optimizedDirectory.canWrite())) {                throw new IllegalArgumentException(                        "optimizedDirectory not readable/writable: "                        + optimizedDirectory);            }        }        this.definingContext = definingContext;        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);    }
DexPathList的构造函数

先进行一些验证,然后关键是makeDexElements和splitLibraryPath函数

先看第一个makeDexElements

    /**     * Makes an array of dex/resource path elements, one per element of     * the given array.     */    private static Element[] makeDexElements(ArrayList<File> files,            File optimizedDirectory) {        ArrayList<Element> elements = new ArrayList<Element>();        /*         * Open all files and load the (direct or contained) dex files         * up front.         */        for (File file : files) {            File zip = null;            DexFile dex = null;            String name = file.getName();            if (name.endsWith(DEX_SUFFIX)) {                // Raw dex file (not inside a zip/jar).                try {                    dex = loadDexFile(file, optimizedDirectory);                } catch (IOException ex) {                    System.logE("Unable to load dex file: " + file, ex);                }            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)                    || name.endsWith(ZIP_SUFFIX)) {                zip = file;                try {                    dex = loadDexFile(file, optimizedDirectory);                } catch (IOException ignored) {                    /*                     * IOException might get thrown "legitimately" by                     * the DexFile constructor if the zip file turns                     * out to be resource-only (that is, no                     * classes.dex file in it). Safe to just ignore                     * the exception here, and let dex == null.                     */                }            } else if (file.isDirectory()) {                // We support directories for looking up resources.                // This is only useful for running libcore tests.                elements.add(new Element(file, true, null, null));            } else {                System.logW("Unknown file type for: " + file);            }            if ((zip != null) || (dex != null)) {                elements.add(new Element(file, false, zip, dex));            }        }        return elements.toArray(new Element[elements.size()]);    }
注释说的很清楚,产生一个dex或者resource元素的数组

如果文件以.dex、.apk、.jar和.zip结尾,都会调用loadDexFile,区别在于局部变量zip的赋值(具体看代码),如果路径是一个目录,则直接添加到array

前四种情况,都会在调用完loadDexFile后再添加

这里还有个小问题,makeDexElements函数的第一个参数,在调用时为splitDexPath(dexPath)

splitDexPath函数如下

    private static ArrayList<File> splitDexPath(String path) {        return splitPaths(path, null, false);    }
splitPaths函数如下

    private static ArrayList<File> splitPaths(String path1, String path2,            boolean wantDirectories) {        ArrayList<File> result = new ArrayList<File>();        splitAndAdd(path1, wantDirectories, result);        splitAndAdd(path2, wantDirectories, result);        return result;    }
最终的处理在splitAndAdd函数中

    private static void splitAndAdd(String searchPath, boolean directoriesOnly,            ArrayList<File> resultList) {        if (searchPath == null) {            return;        }        for (String path : searchPath.split(":")) {            try {                StructStat sb = Libcore.os.stat(path);                if (!directoriesOnly || S_ISDIR(sb.st_mode)) {                    resultList.add(new File(path));                }            } catch (ErrnoException ignored) {            }        }    }
回到主线任务,下面看loadDexFile函数

    private static DexFile loadDexFile(File file, File optimizedDirectory)            throws IOException {        if (optimizedDirectory == null) {            return new DexFile(file);        } else {            String optimizedPath = optimizedPathFor(file, optimizedDirectory);            return DexFile.loadDex(file.getPath(), optimizedPath, 0);        }    }
还记得吗,由于PathClassLoader的optimizedDirectory为null,所以这里调用的是new DexFile(file)

自然DexClassLoader调用的就是DexFile.loadDex(file.getPath(), optimizedPath, 0);
先看loadDex

    static public DexFile loadDex(String sourcePathName, String outputPathName,        int flags) throws IOException {        /*         * TODO: we may want to cache previously-opened DexFile objects.         * The cache would be synchronized with close().  This would help         * us avoid mapping the same DEX more than once when an app         * decided to open it multiple times.  In practice this may not         * be a real issue.         */        return new DexFile(sourcePathName, outputPathName, flags);    }
其实也就是DexFile的构造函数

    private DexFile(String sourceName, String outputName, int flags) throws IOException {        if (outputName != null) {            try {                String parent = new File(outputName).getParent();                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {                    throw new IllegalArgumentException("Optimized data directory " + parent                            + " is not owned by the current user. Shared storage cannot protect"                            + " your application from code injection attacks.");                }            } catch (ErrnoException ignored) {                // assume we'll fail with a more contextual error later            }        }        mCookie = openDexFile(sourceName, outputName, flags);        mFileName = sourceName;        guard.open("close");        //System.out.println("DEX FILE cookie is " + mCookie);    }
首先,如果是DexClassLoader,会先检查父目录的所有者。

无论是哪种ClassLoader,最终都会调用openDexFile

这是一个native函数,位于dalvik\vm\native\dalvik_system_DexFile.cpp

const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {    { "openDexFile",        "(Ljava/lang/String;Ljava/lang/String;I)I",        Dalvik_dalvik_system_DexFile_openDexFile },    { "openDexFile",        "([B)I",        Dalvik_dalvik_system_DexFile_openDexFile_bytearray },    { "closeDexFile",       "(I)V",        Dalvik_dalvik_system_DexFile_closeDexFile },    { "defineClass",        "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",        Dalvik_dalvik_system_DexFile_defineClass },    { "getClassNameList",   "(I)[Ljava/lang/String;",        Dalvik_dalvik_system_DexFile_getClassNameList },    { "isDexOptNeeded",     "(Ljava/lang/String;)Z",        Dalvik_dalvik_system_DexFile_isDexOptNeeded },    { NULL, NULL, NULL },};
根据这个二维字符串数组,找到openDexFile所对应的函数

要注意函数参数,别找错

static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,    JValue* pResult){    StringObject* sourceNameObj = (StringObject*) args[0];    StringObject* outputNameObj = (StringObject*) args[1];    DexOrJar* pDexOrJar = NULL;    JarFile* pJarFile;    RawDexFile* pRawDexFile;    char* sourceName;    char* outputName;    if (sourceNameObj == NULL) {        dvmThrowNullPointerException("sourceName == null");        RETURN_VOID();    }    sourceName = dvmCreateCstrFromString(sourceNameObj);    if (outputNameObj != NULL)        outputName = dvmCreateCstrFromString(outputNameObj);    else        outputName = NULL;    /*     * We have to deal with the possibility that somebody might try to     * open one of our bootstrap class DEX files.  The set of dependencies     * will be different, and hence the results of optimization might be     * different, which means we'd actually need to have two versions of     * the optimized DEX: one that only knows about part of the boot class     * path, and one that knows about everything in it.  The latter might     * optimize field/method accesses based on a class that appeared later     * in the class path.     *     * We can't let the user-defined class loader open it and start using     * the classes, since the optimized form of the code skips some of     * the method and field resolution that we would ordinarily do, and     * we'd have the wrong semantics.     *     * We have to reject attempts to manually open a DEX file from the boot     * class path.  The easiest way to do this is by filename, which works     * out because variations in name (e.g. "/system/framework/./ext.jar")     * result in us hitting a different dalvik-cache entry.  It's also fine     * if the caller specifies their own output file.     */    if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {        ALOGW("Refusing to reopen boot DEX '%s'", sourceName);        dvmThrowIOException(            "Re-opening BOOTCLASSPATH DEX files is not allowed");        free(sourceName);        free(outputName);        RETURN_VOID();    }    /*     * Try to open it directly as a DEX if the name ends with ".dex".     * If that fails (or isn't tried in the first place), try it as a     * Zip with a "classes.dex" inside.     */    if (hasDexExtension(sourceName)            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {        ALOGV("Opening DEX file '%s' (DEX)", sourceName);        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));        pDexOrJar->isDex = true;        pDexOrJar->pRawDexFile = pRawDexFile;        pDexOrJar->pDexMemory = NULL;    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {        ALOGV("Opening DEX file '%s' (Jar)", sourceName);        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));        pDexOrJar->isDex = false;        pDexOrJar->pJarFile = pJarFile;        pDexOrJar->pDexMemory = NULL;    } else {        ALOGV("Unable to open DEX file '%s'", sourceName);        dvmThrowIOException("unable to open DEX file");    }    if (pDexOrJar != NULL) {        pDexOrJar->fileName = sourceName;        addToDexFileTable(pDexOrJar);    } else {        free(sourceName);    }    free(outputName);    RETURN_PTR(pDexOrJar);}

dvmRawDexFileOpen函数打开一个原生dex文件,优化,加载。

函数挺长,分段看

int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,    RawDexFile** ppRawDexFile, bool isBootstrap){    /*     * TODO: This duplicates a lot of code from dvmJarFileOpen() in     * JarFile.c. This should be refactored.     */    DvmDex* pDvmDex = NULL;    char* cachedName = NULL;    int result = -1;    int dexFd = -1;    int optFd = -1;    u4 modTime = 0;    u4 adler32 = 0;    size_t fileSize = 0;    bool newFile = false;    bool locked = false;    dexFd = open(fileName, O_RDONLY);    if (dexFd < 0) goto bail;    /* If we fork/exec into dexopt, don't let it inherit the open fd. */    dvmSetCloseOnExec(dexFd);
声明变量,打开一下文件,再。。。关上

    if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {        ALOGE("Error with header for %s", fileName);        goto bail;    }    if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {        ALOGE("Error with stat for %s", fileName);        goto bail;    }
之后的两个函数,第一个验证文件魔数,并且获取adler32校验码

第二个函数调用fstat函数,主要是为了获得上次修改时间,和文件大小

    /*     * See if the cached file matches. If so, optFd will become a reference     * to the cached file and will have been seeked to just past the "opt"     * header.     */    if (odexOutputName == NULL) {        cachedName = dexOptGenerateCacheFileName(fileName, NULL);        if (cachedName == NULL)            goto bail;    } else {        cachedName = strdup(odexOutputName);    }    ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)",            fileName, cachedName);    optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,        adler32, isBootstrap, &newFile, /*createIfMissing=*/true);    if (optFd < 0) {        ALOGI("Unable to open or create cache for %s (%s)",                fileName, cachedName);        goto bail;    }
在然后就是核心部分dvmOpenCachedDexFile函数,先往下看,回头再说这个函数

    locked = true;    /*     * If optFd points to a new file (because there was no cached     * version, or the cached version was stale), generate the     * optimized DEX. The file descriptor returned is still locked,     * and is positioned just past the optimization header.     */    if (newFile) {        u8 startWhen, copyWhen, endWhen;        bool result;        off_t dexOffset;        dexOffset = lseek(optFd, 0, SEEK_CUR);        result = (dexOffset > 0);        if (result) {            startWhen = dvmGetRelativeTimeUsec();            result = copyFileToFile(optFd, dexFd, fileSize) == 0;            copyWhen = dvmGetRelativeTimeUsec();        }        if (result) {            result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,                fileName, modTime, adler32, isBootstrap);        }        if (!result) {            ALOGE("Unable to extract+optimize DEX from '%s'", fileName);            goto bail;        }        endWhen = dvmGetRelativeTimeUsec();        ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",            fileName,            (int) (copyWhen - startWhen) / 1000,            (int) (endWhen - copyWhen) / 1000);    }    /*     * Map the cached version.  This immediately rewinds the fd, so it     * doesn't have to be seeked anywhere in particular.     */    if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {        ALOGI("Unable to map cached %s", fileName);        goto bail;    }    if (locked) {        /* unlock the fd */        if (!dvmUnlockCachedDexFile(optFd)) {            /* uh oh -- this process needs to exit or we'll wedge the system */            ALOGE("Unable to unlock DEX file");            goto bail;        }        locked = false;    }    ALOGV("Successfully opened '%s'", fileName);    *ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));    (*ppRawDexFile)->cacheFileName = cachedName;    (*ppRawDexFile)->pDvmDex = pDvmDex;    cachedName = NULL;      // don't free it below    result = 0;bail:    free(cachedName);    if (dexFd >= 0) {        close(dexFd);    }    if (optFd >= 0) {        if (locked)            (void) dvmUnlockCachedDexFile(optFd);        close(optFd);    }    return result;}
比较重要的是dvmDexFileOpenFromFd函数,将dex文件拷贝到内存中
dvmOpenCachedDexFile函数位于/dalvik/vm/analysis/DexPrepare.cpp

也是很长的一个函数,分段看

int dvmOpenCachedDexFile(const char* fileName, const char* cacheFileName,    u4 modWhen, u4 crc, bool isBootstrap, bool* pNewFile, bool createIfMissing){    int fd, cc;    struct stat fdStat, fileStat;    bool readOnly = false;    *pNewFile = false;retry:    /*     * Try to open the cache file.  If we've been asked to,     * create it if it doesn't exist.     */    fd = createIfMissing ? open(cacheFileName, O_CREAT|O_RDWR, 0644) : -1;    if (fd < 0) {        fd = open(cacheFileName, O_RDONLY, 0);        if (fd < 0) {            if (createIfMissing) {                // TODO: write an equivalent of strerror_r that returns a std::string.                const std::string errnoString(strerror(errno));                if (directoryIsValid(cacheFileName)) {                    ALOGE("Can't open dex cache file '%s': %s", cacheFileName, errnoString.c_str());                }            }            return fd;        }        readOnly = true;    } else {        fchmod(fd, 0644);    }

尝试打开cacheFileName文件,否则就创建,根据调用时传递的参数可知,这个已经存在

    /*     * Grab an exclusive lock on the cache file.  If somebody else is     * working on it, we'll block here until they complete.  Because     * we're waiting on an external resource, we go into VMWAIT mode.     */    ALOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)",        cacheFileName, fd, isBootstrap);    ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);    cc = flock(fd, LOCK_EX | LOCK_NB);    if (cc != 0) {        ALOGD("DexOpt: sleeping on flock(%s)", cacheFileName);        cc = flock(fd, LOCK_EX);    }    dvmChangeStatus(NULL, oldStatus);    if (cc != 0) {        ALOGE("Can't lock dex cache '%s': %d", cacheFileName, cc);        close(fd);        return -1;    }    ALOGV("DexOpt:  locked cache file");
这段代码看注释理解,flock函数对fd文件上锁,LOCK_EX代表互斥锁,LOCK_NB代表非阻塞。而在整个上锁期间,如果文件已被上锁,则会一直阻塞,直到flock函数

完成工作。而为了能够完成这一步,需要当前线程环境为VMWAIT,所以在flock函数前后分别用dvmChangeStatus切换当前线程状态(在dalvike启动篇也遇到这个函数,

将当前线程的状态切换)

    cc = fstat(fd, &fdStat);    if (cc != 0) {        ALOGE("Can't stat open file '%s'", cacheFileName);        LOGVV("DexOpt: unlocking cache file %s", cacheFileName);        goto close_fail;    }    cc = stat(cacheFileName, &fileStat);    if (cc != 0 ||        fdStat.st_dev != fileStat.st_dev || fdStat.st_ino != fileStat.st_ino)//st_dev文件所在设备id,st_ino结点编号    {        ALOGD("DexOpt: our open cache file is stale; sleeping and retrying");        LOGVV("DexOpt: unlocking cache file %s", cacheFileName);        flock(fd, LOCK_UN);        close(fd);        usleep(250 * 1000);     /* if something is hosed, don't peg machine */        goto retry;    }
核实设备id和结点编号,但是为什么要用fstat和stat分别打开同一个文件???暂时不清楚

    if (fdStat.st_size == 0) {        if (readOnly) {            ALOGW("DexOpt: file has zero length and isn't writable");            goto close_fail;        }        cc = dexOptCreateEmptyHeader(fd);        if (cc != 0)            goto close_fail;        *pNewFile = true;        ALOGV("DexOpt: successfully initialized new cache file");    } else {        bool expectVerify, expectOpt;        if (gDvm.classVerifyMode == VERIFY_MODE_NONE) {            expectVerify = false;        } else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) {            expectVerify = !isBootstrap;        } else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ {            expectVerify = true;        }        if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) {            expectOpt = false;        } else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||                   gDvm.dexOptMode == OPTIMIZE_MODE_FULL) {            expectOpt = expectVerify;        } else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ {            expectOpt = true;        }        ALOGV("checking deps, expecting vfy=%d opt=%d",            expectVerify, expectOpt);
如果文件size为0,并且无法写入(readonly)就报错,若可以写,就产生一个空文件头。如果size不为0,进行设置

注意,PathClassLoader已经在安装阶段生成了odex,所以此处size部位0。相反,DexClassLoader就会进入size为0的分支,*pNewFile会被设置为true

而通过函数声明可以看出,这个pNewFile的值是这个函数的重要输出。

        if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc,                expectVerify, expectOpt))        {            if (readOnly) {                 if (createIfMissing) {                    ALOGW("Cached DEX '%s' (%s) is stale and not writable",                        fileName, cacheFileName);                }                goto close_fail;            }             ALOGD("ODEX file is stale or bad; removing and retrying (%s)",                cacheFileName);            if (ftruncate(fd, 0) != 0) {                ALOGW("Warning: unable to truncate cache file '%s': %s",                    cacheFileName, strerror(errno));                /* keep going */            }            if (unlink(cacheFileName) != 0) {                ALOGW("Warning: unable to remove cache file '%s': %d %s",                    cacheFileName, errno, strerror(errno));                /* keep going; permission failure should probably be fatal */            }            LOGVV("DexOpt: unlocking cache file %s", cacheFileName);            flock(fd, LOCK_UN);            close(fd);            goto retry;        } else {            ALOGV("DexOpt: good deps in cache file");        }    }    assert(fd >= 0);    return fd;close_fail:    flock(fd, LOCK_UN);    close(fd);    return -1;}
pNewFile设置完之后就没什么了。无非是check一下文件头之类的工作。

回到主线,在看一下dvmRawDexFileOpen

    if (newFile) {        u8 startWhen, copyWhen, endWhen;        bool result;        off_t dexOffset;        dexOffset = lseek(optFd, 0, SEEK_CUR);        result = (dexOffset > 0);        if (result) {            startWhen = dvmGetRelativeTimeUsec();            result = copyFileToFile(optFd, dexFd, fileSize) == 0;            copyWhen = dvmGetRelativeTimeUsec();        }        if (result) {            result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,                fileName, modTime, adler32, isBootstrap);        }        if (!result) {            ALOGE("Unable to extract+optimize DEX from '%s'", fileName);            goto bail;        }        endWhen = dvmGetRelativeTimeUsec();        ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",            fileName,            (int) (copyWhen - startWhen) / 1000,            (int) (endWhen - copyWhen) / 1000);    }
这个newFile就是刚才dvmOpenCachedDexFile输出的bool值

当newFile为true时(也就是DexClassLoader),会执行对dex的优化。获得一个空的文件头(dvmOpenCachedDexFile函数中),检查文件偏移量(lseek函数)

将原dex文件拷贝到有一个文件头的空文件当中,最后在dvmOptimizeFile函数执行优化。这个过程我专门回到上一节的优化过程比较下,与extractAndProcessZip

函数的过程大致相同,虽然函数不同,但是过程都是产生一个空文件 ——> 构造文件头 ——> 拷贝 ——> 优化。

dvmOptimizeDexFile函数同样位于/dalvik/vm/analysis/DexPrepare.cpp

bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap){    const char* lastPart = strrchr(fileName, '/');    if (lastPart != NULL)        lastPart++;    else        lastPart = fileName;    ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap);    pid_t pid;    /*     * This could happen if something in our bootclasspath, which we thought     * was all optimized, got rejected.     */    if (gDvm.optimizing) {        ALOGW("Rejecting recursive optimization attempt on '%s'", fileName);        return false;    }    pid = fork();    if (pid == 0) {        static const int kUseValgrind = 0;        static const char* kDexOptBin = "/bin/dexopt";        static const char* kValgrinder = "/usr/bin/valgrind";        static const int kFixedArgCount = 10;        static const int kValgrindArgCount = 5;        static const int kMaxIntLen = 12;   // '-'+10dig+'\0' -OR- 0x+8dig        int bcpSize = dvmGetBootPathSize();        int argc = kFixedArgCount + bcpSize            + (kValgrindArgCount * kUseValgrind);        const char* argv[argc+1];             // last entry is NULL        char values[argc][kMaxIntLen];        char* execFile;        const char* androidRoot;        int flags;        /* change process groups, so we don't clash with ProcessManager */        setpgid(0, 0);        /* full path to optimizer */        androidRoot = getenv("ANDROID_ROOT");        if (androidRoot == NULL) {            ALOGW("ANDROID_ROOT not set, defaulting to /system");            androidRoot = "/system";        }        execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1);        strcpy(execFile, androidRoot);        strcat(execFile, kDexOptBin);        /*         * Create arg vector.         */        int curArg = 0;        if (kUseValgrind) {            /* probably shouldn't ship the hard-coded path */            argv[curArg++] = (char*)kValgrinder;            argv[curArg++] = "--tool=memcheck";            argv[curArg++] = "--leak-check=yes";        // check for leaks too            argv[curArg++] = "--leak-resolution=med";   // increase from 2 to 4            argv[curArg++] = "--num-callers=16";        // default is 12            assert(curArg == kValgrindArgCount);        }        argv[curArg++] = execFile;        argv[curArg++] = "--dex";        sprintf(values[2], "%d", DALVIK_VM_BUILD);        argv[curArg++] = values[2];        sprintf(values[3], "%d", fd);        argv[curArg++] = values[3];        sprintf(values[4], "%d", (int) dexOffset);        argv[curArg++] = values[4];        sprintf(values[5], "%d", (int) dexLength);        argv[curArg++] = values[5];        argv[curArg++] = (char*)fileName;        sprintf(values[7], "%d", (int) modWhen);        argv[curArg++] = values[7];        sprintf(values[8], "%d", (int) crc);        argv[curArg++] = values[8];        flags = 0;        if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) {            flags |= DEXOPT_OPT_ENABLED;            if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)                flags |= DEXOPT_OPT_ALL;        }        if (gDvm.classVerifyMode != VERIFY_MODE_NONE) {            flags |= DEXOPT_VERIFY_ENABLED;            if (gDvm.classVerifyMode == VERIFY_MODE_ALL)                flags |= DEXOPT_VERIFY_ALL;        }        if (isBootstrap)            flags |= DEXOPT_IS_BOOTSTRAP;        if (gDvm.generateRegisterMaps)            flags |= DEXOPT_GEN_REGISTER_MAPS;        sprintf(values[9], "%d", flags);        argv[curArg++] = values[9];        assert(((!kUseValgrind && curArg == kFixedArgCount) ||               ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));        ClassPathEntry* cpe;        for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {            argv[curArg++] = cpe->fileName;        }        assert(curArg == argc);        argv[curArg] = NULL;        if (kUseValgrind)            execv(kValgrinder, const_cast<char**>(argv));        else            execv(execFile, const_cast<char**>(argv));        ALOGE("execv '%s'%s failed: %s", execFile,            kUseValgrind ? " [valgrind]" : "", strerror(errno));        exit(1);    } else {        ALOGV("DexOpt: waiting for verify+opt, pid=%d", (int) pid);        int status;        pid_t gotPid;        /*         * Wait for the optimization process to finish.  We go into VMWAIT         * mode here so GC suspension won't have to wait for us.         */        ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);        while (true) {            gotPid = waitpid(pid, &status, 0);            if (gotPid == -1 && errno == EINTR) {                ALOGD("waitpid interrupted, retrying");            } else {                break;            }        }        dvmChangeStatus(NULL, oldStatus);        if (gotPid != pid) {            ALOGE("waitpid failed: wanted %d, got %d: %s",                (int) pid, (int) gotPid, strerror(errno));            return false;        }        if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {            ALOGD("DexOpt: --- END '%s' (success) ---", lastPart);            return true;        } else {            ALOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed",                lastPart, status);            return false;        }    }}

这个函数的优化很简单,也是执行/bin/dexopt

插播一句,在脱壳时经常使用的dvmDexFileOpenPartial函数的调用过程

/system/bin/dexopt的main函数开始 ——> 中间函数 ——> extractAndProcessZip ——> dvmContinueOptimization ——> dvmDexFileOpenPartial

回到dvmRawDexFileOpen函数

接下来执行dvmDexFileOpenFromFd函数,位于dalvik\vm\DvmDex.cpp

int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex){    DvmDex* pDvmDex;    DexFile* pDexFile;    MemMapping memMap;    int parseFlags = kDexParseDefault;    int result = -1;    if (gDvm.verifyDexChecksum)        parseFlags |= kDexParseVerifyChecksum;    if (lseek(fd, 0, SEEK_SET) < 0) {        ALOGE("lseek rewind failed");        goto bail;    }    if (sysMapFileInShmemWritableReadOnly(fd, &memMap) != 0) {        ALOGE("Unable to map file");        goto bail;    }    pDexFile = dexFileParse((u1*)memMap.addr, memMap.length, parseFlags);    if (pDexFile == NULL) {        ALOGE("DEX parse failed");        sysReleaseShmem(&memMap);        goto bail;    }    pDvmDex = allocateAuxStructures(pDexFile);    if (pDvmDex == NULL) {        dexFileFree(pDexFile);        sysReleaseShmem(&memMap);        goto bail;    }    /* tuck this into the DexFile so it gets released later */    sysCopyMap(&pDvmDex->memMap, &memMap);    pDvmDex->isMappedReadOnly = true;    *ppDvmDex = pDvmDex;    result = 0;bail:    return result;}
终于有个不长的函数了

sysMapFileInShmemWritableReadOnly函数将优化后的dex加载进内存,函数位于dalvik\libdex\SysUtil.cpp

int sysMapFileInShmemWritableReadOnly(int fd, MemMapping* pMap){#ifdef HAVE_POSIX_FILEMAP    off_t start;    size_t length;    void* memPtr;    assert(pMap != NULL);    if (getFileStartAndLength(fd, &start, &length) < 0)        return -1;    memPtr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE,            fd, start);    if (memPtr == MAP_FAILED) {        ALOGW("mmap(%d, R/W, FILE|PRIVATE, %d, %d) failed: %s", (int) length,            fd, (int) start, strerror(errno));        return -1;    }    if (mprotect(memPtr, length, PROT_READ) < 0) {        /* this fails with EACCESS on FAT filesystems, e.g. /sdcard */        int err = errno;        ALOGV("mprotect(%p, %d, PROT_READ) failed: %s",            memPtr, length, strerror(err));        ALOGD("mprotect(RO) failed (%d), file will remain read-write", err);    }    pMap->baseAddr = pMap->addr = memPtr;    pMap->baseLength = pMap->length = length;    return 0;#else    return sysFakeMapFile(fd, pMap);#endif}
先获得文件头位置和长度,然后用mmap函数映射到内存当中

之后执行dexFileParse,位于dalvik\libdex\DexFile.cpp

又是一个老长的函数,分段看

DexFile* dexFileParse(const u1* data, size_t length, int flags){    DexFile* pDexFile = NULL;    const DexHeader* pHeader;    const u1* magic;    int result = -1;    if (length < sizeof(DexHeader)) {        ALOGE("too short to be a valid .dex");        goto bail;      /* bad file format */    }    pDexFile = (DexFile*) malloc(sizeof(DexFile));    if (pDexFile == NULL)        goto bail;      /* alloc failure */    memset(pDexFile, 0, sizeof(DexFile));
检查文件大小,分配内存

    if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {//验证文件魔数        magic = data;        if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {            ALOGE("bad opt version (0x%02x %02x %02x %02x)",                 magic[4], magic[5], magic[6], magic[7]);            goto bail;        }        pDexFile->pOptHeader = (const DexOptHeader*) data;//文件头部        ALOGV("Good opt header, DEX offset is %d, flags=0x%02x",            pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags);        /* parse the optimized dex file tables */        if (!dexParseOptData(data, length, pDexFile))//将优化数据与DexFile文件数据结构中相应的成员进行关联            goto bail;        /* ignore the opt header and appended data from here on out */        data += pDexFile->pOptHeader->dexOffset;        length -= pDexFile->pOptHeader->dexOffset;        if (pDexFile->pOptHeader->dexLength > length) {            ALOGE("File truncated? stored len=%d, rem len=%d",                pDexFile->pOptHeader->dexLength, (int) length);            goto bail;        }        length = pDexFile->pOptHeader->dexLength;    }
    dexFileSetupBasicPointers(pDexFile, data);//其他各部分数据与DexFile数据结构建立完整的映射关系    pHeader = pDexFile->pHeader;    if (!dexHasValidMagic(pHeader)) {        goto bail;    }    /*     * Verify the checksum(s).  This is reasonably quick, but does require     * touching every byte in the DEX file.  The base checksum changes after     * byte-swapping and DEX optimization.     */    if (flags & kDexParseVerifyChecksum) {        u4 adler = dexComputeChecksum(pHeader);        if (adler != pHeader->checksum) {            ALOGE("ERROR: bad checksum (%08x vs %08x)",                adler, pHeader->checksum);            if (!(flags & kDexParseContinueOnError))                goto bail;        } else {            ALOGV("+++ adler32 checksum (%08x) verified", adler);        }        const DexOptHeader* pOptHeader = pDexFile->pOptHeader;        if (pOptHeader != NULL) {            adler = dexComputeOptChecksum(pOptHeader);            if (adler != pOptHeader->checksum) {                ALOGE("ERROR: bad opt checksum (%08x vs %08x)",                    adler, pOptHeader->checksum);                if (!(flags & kDexParseContinueOnError))                    goto bail;            } else {                ALOGV("+++ adler32 opt checksum (%08x) verified", adler);            }        }    }    /*     * Verify the SHA-1 digest.  (Normally we don't want to do this --     * the digest is used to uniquely identify the original DEX file, and     * can't be computed for verification after the DEX is byte-swapped     * and optimized.)     */    if (kVerifySignature) {        unsigned char sha1Digest[kSHA1DigestLen];        const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) +                            kSHA1DigestLen;        dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest);        if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) {            char tmpBuf1[kSHA1DigestOutputLen];            char tmpBuf2[kSHA1DigestOutputLen];            ALOGE("ERROR: bad SHA1 digest (%s vs %s)",                dexSHA1DigestToStr(sha1Digest, tmpBuf1),                dexSHA1DigestToStr(pHeader->signature, tmpBuf2));            if (!(flags & kDexParseContinueOnError))                goto bail;        } else {            ALOGV("+++ sha1 digest verified");        }    }    if (pHeader->fileSize != length) {        ALOGE("ERROR: stored file size (%d) != expected (%d)",            (int) pHeader->fileSize, (int) length);        if (!(flags & kDexParseContinueOnError))            goto bail;    }    if (pHeader->classDefsSize == 0) {        ALOGE("ERROR: DEX file has no classes in it, failing");        goto bail;    }    /*     * Success!     */    result = 0;bail:    if (result != 0 && pDexFile != NULL) {        dexFileFree(pDexFile);        pDexFile = NULL;    }    return pDexFile;}
执行完之后回到dvmDexFileOpenFromFd函数

allocateAuxStructures函数用来生成DvmDex结构

static DvmDex* allocateAuxStructures(DexFile* pDexFile){    DvmDex* pDvmDex;    const DexHeader* pHeader;    u4 stringSize, classSize, methodSize, fieldSize;    pHeader = pDexFile->pHeader;    stringSize = pHeader->stringIdsSize * sizeof(struct StringObject*);    classSize  = pHeader->typeIdsSize * sizeof(struct ClassObject*);    methodSize = pHeader->methodIdsSize * sizeof(struct Method*);    fieldSize  = pHeader->fieldIdsSize * sizeof(struct Field*);    u4 totalSize = sizeof(DvmDex) +                   stringSize + classSize + methodSize + fieldSize;    u1 *blob = (u1 *)dvmAllocRegion(totalSize,                              PROT_READ | PROT_WRITE, "dalvik-aux-structure");    if ((void *)blob == MAP_FAILED)        return NULL;    pDvmDex = (DvmDex*)blob;    blob += sizeof(DvmDex);    pDvmDex->pDexFile = pDexFile;    pDvmDex->pHeader = pHeader;    pDvmDex->pResStrings = (struct StringObject**)blob;    blob += stringSize;    pDvmDex->pResClasses = (struct ClassObject**)blob;    blob += classSize;    pDvmDex->pResMethods = (struct Method**)blob;    blob += methodSize;    pDvmDex->pResFields = (struct Field**)blob;    ALOGV("+++ DEX %p: allocateAux (%d+%d+%d+%d)*4 = %d bytes",        pDvmDex, stringSize/4, classSize/4, methodSize/4, fieldSize/4,        stringSize + classSize + methodSize + fieldSize);    pDvmDex->pInterfaceCache = dvmAllocAtomicCache(DEX_INTERFACE_CACHE_SIZE);    dvmInitMutex(&pDvmDex->modLock);    return pDvmDex;}
回到dvmDexFileOpenFromFd

调用sysCopyMap将memMap关联到pDvmDex

执行完之后,回到dvmRawDexFileOpen

    *ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));    (*ppRawDexFile)->cacheFileName = cachedName;    (*ppRawDexFile)->pDvmDex = pDvmDex;    cachedName = NULL;      // don't free it below    result = 0;
将ppRawDexFile的成员都关联起来

完成

灵魂作画,大概这样

dvmRawDexFileOpen执行完成了

回到开始的Dalvik_dalvik_system_DexFile_openDexFile

    if (hasDexExtension(sourceName)            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {        ALOGV("Opening DEX file '%s' (DEX)", sourceName);        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));        pDexOrJar->isDex = true;        pDexOrJar->pRawDexFile = pRawDexFile;        pDexOrJar->pDexMemory = NULL;    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {        ALOGV("Opening DEX file '%s' (Jar)", sourceName);        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));        pDexOrJar->isDex = false;        pDexOrJar->pJarFile = pJarFile;        pDexOrJar->pDexMemory = NULL;    } else {        ALOGV("Unable to open DEX file '%s'", sourceName);        dvmThrowIOException("unable to open DEX file");    }    if (pDexOrJar != NULL) {        pDexOrJar->fileName = sourceName;        addToDexFileTable(pDexOrJar);    } else {        free(sourceName);    }
可以看到另一个分支为dvmJarFileOpen

该函数位于dalvik\vm\JarFile.cpp

int dvmJarFileOpen(const char* fileName, const char* odexOutputName,    JarFile** ppJarFile, bool isBootstrap){    /*     * TODO: This function has been duplicated and modified to become     * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.     */    ZipArchive archive;    DvmDex* pDvmDex = NULL;    char* cachedName = NULL;    bool archiveOpen = false;    bool locked = false;    int fd = -1;    int result = -1;    /* 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);        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".         */        entry = dexZipFindEntry(&archive, kDexInJarName);        if (entry != NULL) {            bool newFile = false;            /*             * We've found the one we want.  See if there's an up-to-date copy             * in the cache.             *             * On return, "fd" will be seeked just past the "opt" header.             *             * If a stale .odex file is present and classes.dex exists in             * the archive, this will *not* return an fd pointing to the             * .odex file; the fd will point into dalvik-cache like any             * other jar.             */            if (odexOutputName == NULL) {                cachedName = dexOptGenerateCacheFileName(fileName,                                kDexInJarName);                if (cachedName == NULL)                    goto bail;            } else {                cachedName = strdup(odexOutputName);            }            ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",                fileName, cachedName);            fd = dvmOpenCachedDexFile(fileName, cachedName,                    dexGetZipEntryModTime(&archive, entry),                    dexGetZipEntryCrc32(&archive, entry),                    isBootstrap, &newFile, /*createIfMissing=*/true);            if (fd < 0) {                ALOGI("Unable to open or create cache for %s (%s)",                    fileName, cachedName);                goto bail;            }            locked = true;            /*             * If fd points to a new file (because there was no cached version,             * or the cached version was stale), generate the optimized DEX.             * The file descriptor returned is still locked, and is positioned             * just past the optimization header.             */            if (newFile) {                u8 startWhen, extractWhen, endWhen;                bool result;                off_t dexOffset;                dexOffset = lseek(fd, 0, SEEK_CUR);                result = (dexOffset > 0);                if (result) {                    startWhen = dvmGetRelativeTimeUsec();                    result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;                    extractWhen = dvmGetRelativeTimeUsec();                }                if (result) {                    result = dvmOptimizeDexFile(fd, dexOffset,                                dexGetZipEntryUncompLen(&archive, entry),                                fileName,                                dexGetZipEntryModTime(&archive, entry),                                dexGetZipEntryCrc32(&archive, entry),                                isBootstrap);                }                if (!result) {                    ALOGE("Unable to extract+optimize DEX from '%s'",                        fileName);                    goto bail;                }                endWhen = dvmGetRelativeTimeUsec();                ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",                    fileName,                    (int) (extractWhen - startWhen) / 1000,                    (int) (endWhen - extractWhen) / 1000);            }        } else {            ALOGI("Zip is good, but no %s inside, and no valid .odex "                    "file in the same directory", kDexInJarName);            goto bail;        }    }    /*     * Map the cached version.  This immediately rewinds the fd, so it     * doesn't have to be seeked anywhere in particular.     */    if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {        ALOGI("Unable to map %s in %s", kDexInJarName, fileName);        goto bail;    }    if (locked) {        /* unlock the fd */        if (!dvmUnlockCachedDexFile(fd)) {            /* uh oh -- this process needs to exit or we'll wedge the system */            ALOGE("Unable to unlock DEX file");            goto bail;        }        locked = false;    }    ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);    *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));    (*ppJarFile)->archive = archive;    (*ppJarFile)->cacheFileName = cachedName;    (*ppJarFile)->pDvmDex = pDvmDex;    cachedName = NULL;      // don't free it below    result = 0;bail:    /* clean up, closing the open file */    if (archiveOpen && result != 0)        dexZipCloseArchive(&archive);    free(cachedName);    if (fd >= 0) {        if (locked)            (void) dvmUnlockCachedDexFile(fd);        close(fd);    }    return result;}
dvmRawDexFileOpen一样,也很多

不过过程大同小异,无非是函数不一样,每一步的目的都是相同的

dvmOpenCachedDexFile、dvmOptimizeDexFile、dvmDexFileOpenFromFd这些关键函数没有变。

简单看一下,就不做详细分析了。

这篇,从上周写到这周,内容超多,还是要时常会看,复习。总结下来,关键步骤也就那几步。


































0 0
原创粉丝点击