欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]dvm探讨之odex绕过

[原创]dvm探讨之odex绕过

最近研究有关odex的相关资料,目的是想在user模式下,修改system.img中framework的odex,因为绕不过签名检测这关总是自动重启。
因为看了很多,也想了很多,这里只是抛砖引玉,说的不好请各位见谅。
分析步骤:
1、寻找dvm初始化代码,弄明白dvm初始化流程。
2、寻找dexopt优化和签名流程。
dalvik虚拟机里面我们主要分析两个目录:“vm”和“dexopt”
他们构建后分别生成:“libdvm.so”和“dexopt”
1、libdvm.so 初始化流程分析
vm/Init.cpp ->dvmStartup


/*

 * VM initialization.  Pass in any options provided on the command line.

 * Do not pass in the class name or the options for the class.

 *

 * Returns 0 on success.

 */

std::string dvmStartup(int argc, const char* const argv[],

        bool ignoreUnrecognized, JNIEnv* pEnv)

{

    ScopedShutdown scopedShutdown;

    assert(gDvm.initializing);

    ALOGV("VM init args (%d):", argc);

    for (int i = 0; i < argc; i++) {

        ALOGV("  %d: '%s'", i, argv[i]);

    }

    setCommandLineDefaults();

    /*

     * Process the option flags (if any).

     */

    int cc = processOptions(argc, argv, ignoreUnrecognized);

    if (cc != 0) {

        if (cc < 0) {

            dvmFprintf(stderr, "\n");

            usage("dalvikvm");

        }

        return "syntax error";

    }

#if WITH_EXTRA_GC_CHECKS > 1

    /* only "portable" interp has the extra goodies */

    if (gDvm.executionMode != kExecutionModeInterpPortable) {

        ALOGI("Switching to 'portable' interpreter for GC checks");

        gDvm.executionMode = kExecutionModeInterpPortable;

    }

#endif

    /* Configure group scheduling capabilities */

    if (!access("/dev/cpuctl/tasks", F_OK)) {

        ALOGV("Using kernel group scheduling");

        gDvm.kernelGroupScheduling = 1;

    } else {

        ALOGV("Using kernel scheduler policies");

    }

    /* configure signal handling */

    if (!gDvm.reduceSignals)

        blockSignals();

    /* verify system page size */

    if (sysconf(_SC_PAGESIZE) != SYSTEM_PAGE_SIZE) {

        return StringPrintf("expected page size %d, got %d",

                SYSTEM_PAGE_SIZE, (int) sysconf(_SC_PAGESIZE));

    }

    /* mterp setup */

    ALOGV("Using executionMode %d", gDvm.executionMode);

    dvmCheckAsmConstants();

    /*

     * Initialize components.

     */

    dvmQuasiAtomicsStartup();

    if (!dvmAllocTrackerStartup()) {

        return "dvmAllocTrackerStartup failed";

    }

    if (!dvmGcStartup()) {

        return "dvmGcStartup failed";

    }

    if (!dvmThreadStartup()) {

        return "dvmThreadStartup failed";

    }

    if (!dvmInlineNativeStartup()) {

        return "dvmInlineNativeStartup";

    }

    if (!dvmRegisterMapStartup()) {

        return "dvmRegisterMapStartup failed";

    }

    if (!dvmInstanceofStartup()) {

        return "dvmInstanceofStartup failed";

    }

    if (!dvmClassStartup()) {

        return "dvmClassStartup failed";

    }

    /*

     * At this point, the system is guaranteed to be sufficiently

     * initialized that we can look up classes and class members. This

     * call populates the gDvm instance with all the class and member

     * references that the VM wants to use directly.

     */

    if (!dvmFindRequiredClassesAndMembers()) {

        return "dvmFindRequiredClassesAndMembers failed";

    }

    if (!dvmStringInternStartup()) {

        return "dvmStringInternStartup failed";

    }

    if (!dvmNativeStartup()) {

        return "dvmNativeStartup failed";

    }

    if (!dvmInternalNativeStartup()) {

        return "dvmInternalNativeStartup failed";

    }

    if (!dvmJniStartup()) {

        return "dvmJniStartup failed";

    }

    if (!dvmProfilingStartup()) {

        return "dvmProfilingStartup failed";

    }

    /*

     * Create a table of methods for which we will substitute an "inline"

     * version for performance.

     */

    if (!dvmCreateInlineSubsTable()) {

        return "dvmCreateInlineSubsTable failed";

    }

    /*

     * Miscellaneous class library validation.

     */

    if (!dvmValidateBoxClasses()) {

        return "dvmValidateBoxClasses failed";

    }

    /*

     * Do the last bits of Thread struct initialization we need to allow

     * JNI calls to work.

     */

    if (!dvmPrepMainForJni(pEnv)) {

        return "dvmPrepMainForJni failed";

    }

    /*

     * Explicitly initialize java.lang.Class.  This doesn't happen

     * automatically because it's allocated specially (it's an instance

     * of itself).  Must happen before registration of system natives,

     * which make some calls that throw assertions if the classes they

     * operate on aren't initialized.

     */

    if (!dvmInitClass(gDvm.classJavaLangClass)) {

        return "couldn't initialized java.lang.Class";

    }

    /*

     * Register the system native methods, which are registered through JNI.

     */

    if (!registerSystemNatives(pEnv)) {

        return "couldn't register system natives";

    }

    /*

     * Do some "late" initialization for the memory allocator.  This may

     * allocate storage and initialize classes.

     */

    if (!dvmCreateStockExceptions()) {

        return "dvmCreateStockExceptions failed";

    }

    /*

     * At this point, the VM is in a pretty good state.  Finish prep on

     * the main thread (specifically, create a java.lang.Thread object to go

     * along with our Thread struct).  Note we will probably be executing

     * some interpreted class initializer code in here.

     */

    if (!dvmPrepMainThread()) {

        return "dvmPrepMainThread failed";

    }

    /*

     * Make sure we haven't accumulated any tracked references.  The main

     * thread should be starting with a clean slate.

     */

    if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)

    {

        ALOGW("Warning: tracked references remain post-initialization");

        dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");

    }

    /* general debugging setup */

    if (!dvmDebuggerStartup()) {

        return "dvmDebuggerStartup failed";

    }

    if (!dvmGcStartupClasses()) {

        return "dvmGcStartupClasses failed";

    }

    /*

     * Init for either zygote mode or non-zygote mode.  The key difference

     * is that we don't start any additional threads in Zygote mode.

     */

    if (gDvm.zygote) {

        if (!initZygote()) {

            return "initZygote failed";

        }

    } else {

        if (!dvmInitAfterZygote()) {

            return "dvmInitAfterZygote failed";

        }

    }

#ifndef NDEBUG

    if (!dvmTestHash())

        ALOGE("dvmTestHash FAILED");

    if (false /*noisy!*/ && !dvmTestIndirectRefTable())

        ALOGE("dvmTestIndirectRefTable FAILED");

#endif

    if (dvmCheckException(dvmThreadSelf())) {

        dvmLogExceptionStackTrace();

        return "Exception pending at end of VM initialization";

    }

    scopedShutdown.disarm();

    return "";

}


下一步:
vm/oo/Class.cpp->dvmClassStartup->processClassPath->prepareCpe

/*

 * Prepare a ClassPathEntry struct, which at this point only has a valid

 * filename.  We need to figure out what kind of file it is, and for

 * everything other than directories we need to open it up and see

 * what's inside.

 */

static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)

{

    struct stat sb;

    if (stat(cpe->fileName, &sb) < 0) {

        ALOGD("Unable to stat classpath element '%s'", cpe->fileName);

        return false;

    }

    if (S_ISDIR(sb.st_mode)) {

        ALOGE("Directory classpath elements are not supported: %s", cpe->fileName);

        return false;

    }

    char suffix[10];

    getFileNameSuffix(cpe->fileName, suffix, sizeof(suffix));

    if ((strcmp(suffix, "jar") == 0) || (strcmp(suffix, "zip") == 0) ||

            (strcmp(suffix, "apk") == 0)) {

        JarFile* pJarFile = NULL;

        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;

        if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0) {

            cpe->kind = kCpeDex;

            cpe->ptr = pRawDexFile;

            return true;

        }

    } else {

        ALOGE("Unknown type suffix '%s'", suffix);

    }

    ALOGD("Unable to process classpath element '%s'", cpe->fileName);

    return false;

}


下一步:
vm/JarFile.cpp -> dvmJarFileOpen

/*

 * Open a Jar file.  It's okay if it's just a Zip archive without all of

 * the Jar trimmings, but we do insist on finding "classes.dex" inside

 * or an appropriately-named ".odex" file alongside.

 *

 * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as

 * being part of a different class loader.

 */

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;

}


找到关键函数就在 dvmJarFileOpen 这里。。。。

//我们可以看到它会优先判断该jar或者apk是否有odex文件,如果有就用dvmCheckOptHeaderAndDependencies函数检测odex的头

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;


最后我们来看看dvmCheckOptHeaderAndDependencies干了些什么?
vm/analysis/DexPrepare.cpp -> dvmCheckOptHeaderAndDependencies
附带odex头结构:

struct DexOptHeader {

    u1  magic[8];           /* includes version number */

    u4  dexOffset;          /* file offset of DEX header */

    u4  dexLength;

    u4  depsOffset;         /* offset of optimized DEX dependency table */

    u4  depsLength;

    u4  optOffset;          /* file offset of optimized data tables */

    u4  optLength;

    u4  flags;              /* some info flags */

    u4  checksum;           /* adler32 checksum covering deps/opt */

    /* pad for 64-bit alignment if necessary */

};



bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,

    u4 crc, bool expectVerify, bool expectOpt)

{

    DexOptHeader optHdr; //opt(odex简称)的头结构

    u1* depData = NULL;//opt依赖文件数据(从opt头结构中获取)

    const u1* magic;//魔术头

    off_t posn;//记录偏移量

    int result = false;//最终结果返回

    ssize_t actual;

    /*

     * 将fd指针移动到头部做好读取准备

     */

    if (lseek(fd, 0, SEEK_SET) != 0) {

        ALOGE("DexOpt: failed to seek to start of file: %s", strerror(errno));

        goto bail;

    }

    /*

     * 读取头部数据到结构optHdr

     */

    actual = read(fd, &optHdr, sizeof(optHdr));

    if (actual < 0) {

        ALOGE("DexOpt: failed reading opt header: %s", strerror(errno));

        goto bail;

    } else if (actual != sizeof(optHdr)) {

        ALOGE("DexOpt: failed reading opt header (got %d of %zd)",

            (int) actual, sizeof(optHdr));

        goto bail;

    }

//魔术头判断,保证必须是dey\n036\0的odex魔术头

    magic = optHdr.magic;

    if (memcmp(magic, DEX_MAGIC, 4) == 0) {

        /* somebody probably pointed us at the wrong file */

        ALOGD("DexOpt: expected optimized DEX, found unoptimized");

        goto bail;

    } else if (memcmp(magic, DEX_OPT_MAGIC, 4) != 0) {

        /* not a DEX file, or previous attempt was interrupted */

        ALOGD("DexOpt: incorrect opt magic number (0x%02x %02x %02x %02x)",

            magic[0], magic[1], magic[2], magic[3]);

        goto bail;

    }

    if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {

        ALOGW("DexOpt: stale opt version (0x%02x %02x %02x %02x)",

            magic[4], magic[5], magic[6], magic[7]);

        goto bail;

    }

//确保deps大小在系统规定范围内

    if (optHdr.depsLength < kMinDepSize || optHdr.depsLength > kMaxDepSize) {

        ALOGW("DexOpt: weird deps length %d, bailing", optHdr.depsLength);

        goto bail;

    }

    /*

     * 根据opt头部flags验证,所以odex的flags必须修复,才可能绕过odex这一步。

     */

    {

        const u4 matchMask = DEX_OPT_FLAG_BIG;

        u4 expectedFlags = 0;

#if __BYTE_ORDER != __LITTLE_ENDIAN

        expectedFlags |= DEX_OPT_FLAG_BIG;

#endif

        if ((expectedFlags & matchMask) != (optHdr.flags & matchMask)) {

            ALOGI("DexOpt: header flag mismatch (0x%02x vs 0x%02x, mask=0x%02x)",

                expectedFlags, optHdr.flags, matchMask);

            goto bail;

        }

    }

// 指针移动到deps数据段

    posn = lseek(fd, optHdr.depsOffset, SEEK_SET);

    if (posn < 0) {

        ALOGW("DexOpt: seek to deps failed: %s", strerror(errno));

        goto bail;

    }

    /*

     * 读取deps数据到depData

     */

    depData = (u1*) malloc(optHdr.depsLength);

    if (depData == NULL) {

        ALOGW("DexOpt: unable to allocate %d bytes for deps",

            optHdr.depsLength);

        goto bail;

    }

    actual = read(fd, depData, optHdr.depsLength);

    if (actual < 0) {

        ALOGW("DexOpt: failed reading deps: %s", strerror(errno));

        goto bail;

    } else if (actual != (ssize_t) optHdr.depsLength) {

        ALOGW("DexOpt: failed reading deps: got %d of %d",

            (int) actual, optHdr.depsLength);

        goto bail;

    }

    /*

     * 简单的对dep的数据进行匹配

     */

    const u1* ptr;

    u4 val;

    ptr = depData;

    val = read4LE(&ptr);

    //源文件时间匹配

    if (sourceAvail && val != modWhen) {

        ALOGI("DexOpt: source file mod time mismatch (%08x vs %08x)",

            val, modWhen);

        goto bail;

    }

    val = read4LE(&ptr);

    //CRC匹配

    if (sourceAvail && val != crc) {

        ALOGI("DexOpt: source file CRC mismatch (%08x vs %08x)", val, crc);

        goto bail;

    }

    val = read4LE(&ptr);

    //VM构建版本匹配

    if (val != DALVIK_VM_BUILD) {

        ALOGD("DexOpt: VM build version mismatch (%d vs %d)",

            val, DALVIK_VM_BUILD);

        goto bail;

    }

    /*

     * 其他依赖文件必须来自bootclasspath的定义,其中还有签名匹配

     */

    ClassPathEntry* cpe;

    u4 numDeps;

//依赖库数量

    numDeps = read4LE(&ptr);

    ALOGV("+++ DexOpt: numDeps = %d", numDeps);

    for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {

	//获取其中一个cpe(来自bootclasspath)缓存文件

        const char* cacheFileName =

            dvmPathToAbsolutePortion(getCacheFileName(cpe));

        assert(cacheFileName != NULL); /* guaranteed by Class.c */

	//获取cpe的签名

        const u1* signature = getSignature(cpe);

	//获取cpe文件名的字符长度+1

        size_t len = strlen(cacheFileName) +1;

        u4 storedStrLen;

	//deps数量为0也无法通过

        if (numDeps == 0) {

            /* more entries in bootclasspath than in deps list */

            ALOGI("DexOpt: not all deps represented");

            goto bail;

        }

	//读取deps指针的storedStrLen(存储字符长度)

        storedStrLen = read4LE(&ptr);

	//如果dep的字符长度于cpe必须相等,再者dep和cpe的字符比较也必须相等

        if (len != storedStrLen ||

            strcmp(cacheFileName, (const char*) ptr) != 0)

        {

            ALOGI("DexOpt: mismatch dep name: '%s' vs. '%s'",

                cacheFileName, ptr);

            goto bail;

        }

	//dep指针位移到当前数据末尾,也就是dep签名开始处

        ptr += storedStrLen;

	//开始检测cpe的签名与ptr签名是否相等,比较的签名长度kSHA1DigestLen

        if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {

            ALOGI("DexOpt: mismatch dep signature for '%s'", cacheFileName);

            goto bail;

        }

	//dep指针位移到签名结尾,也就是下一个dep stored(数据段)处。

        ptr += kSHA1DigestLen;

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

	//dep num递减

        numDeps--;

    }

 //如果numDeps不等于0,相当于deps的处理没完。

    if (numDeps != 0) {

        /* more entries in deps list than in classpath */

        ALOGI("DexOpt: Some deps went away");

        goto bail;

    }

    // 所有的dep数据指针是否都处理完了这里还检测一遍,可以说检测是相当严格的

    // 避免了一些简单的IDA修改工作,因为ptr=depData已经是指针操作了,如果指针没处理完,这是绕不过去的。想想办法应该可以绕过。

    if (ptr != depData + optHdr.depsLength) {

        ALOGW("DexOpt: Spurious dep data? %d vs %d",

            (int) (ptr - depData), optHdr.depsLength);

        assert(false);

    }

    result = true;

bail:

    free(depData);

    return result;

}


2、dexopt 执行流程分析
dexopt是一个将dex优化成odex的工具,分析它能够掌握优化的原理。
     OptMain.cpp->extractAndProcessZip

static int extractAndProcessZip(int zipFd, int cacheFd,

    const char* debugFileName, bool isBootstrap, const char* bootClassPath,

    const char* dexoptFlagStr)

{

    ZipArchive zippy;

    ZipEntry zipEntry;

    size_t uncompLen;

    long modWhen, crc32;

    off_t dexOffset;

    int err;

    int result = -1;

    int dexoptFlags = 0;        /* bit flags, from enum DexoptFlags */

    DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;

    DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;

    memset(&zippy, 0, sizeof(zippy));

    /* 确保输出文件cacheFd指针指向文件开始处 */

    if (lseek(cacheFd, 0, SEEK_END) != 0) {

        ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);

        goto bail;

    }

    /*

     * 创建一个空文件,并写入odex头结构

     */

    err = dexOptCreateEmptyHeader(cacheFd);

    if (err != 0)

        goto bail;

    /* 记录当前文件位置,上一步创建完后,指针指向了末尾,也就是dexOffset指定的位置 */

    dexOffset = lseek(cacheFd, 0, SEEK_CUR);

    if (dexOffset < 0)

        goto bail;

    /*

     * 打开zip工具,找到dex文件实体

     */

    if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {

        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);

        goto bail;

    }

    zipEntry = dexZipFindEntry(&zippy, kClassesDex);

    if (zipEntry == NULL) {

        ALOGW("DexOptZ: zip archive '%s' does not include %s",

            debugFileName, kClassesDex);

        goto bail;

    }

    /*

     * 提取zip压缩包的一些基本信息 uncompLen,modWhen,crc32

     */

    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,

            &modWhen, &crc32) != 0)

    {

        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);

        goto bail;

    }

    // 从上面分析odex初始化流程里可以发现,modWhen和crc验证是我们需要关注的。

    uncompLen = uncompLen;//这个值最终不做验证

    modWhen = modWhen;//这个需要验证

    crc32 = crc32;//这个需要验证

    /*

     * 提取zip压缩包内的dex文件实体到cacheFd

     */

    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {

        ALOGW("DexOptZ: extraction of %s from %s failed",

            kClassesDex, debugFileName);

        goto bail;

    }

    /* 优化选项 */

    if (dexoptFlagStr[0] != '\0') {

        const char* opc;

        const char* val;

        opc = strstr(dexoptFlagStr, "v=");      /* 验证 */

        if (opc != NULL) {

            switch (*(opc+2)) {

            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;

            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;

            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;

            default:                                            break;

            }

        }

        opc = strstr(dexoptFlagStr, "o=");      /* 优化 */

        if (opc != NULL) {

            switch (*(opc+2)) {

            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;

            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;

            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;

            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;

            default:                                            break;

            }

        }

        opc = strstr(dexoptFlagStr, "m=y");     /* 寄存器映射 */

        if (opc != NULL) {

            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;

        }

        opc = strstr(dexoptFlagStr, "u=");      /* 单处理器的目标 */

        if (opc != NULL) {

            switch (*(opc+2)) {

            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;

            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;

            default:                                            break;

            }

        }

    }

    /*

     * 准备好初始化VM,并执行优化

     */

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,

            dexoptFlags) != 0)

    {

        ALOGE("DexOptZ: VM init failed");

        goto bail;

    }

    //vmStarted = 1;

    /* 执行优化 */

    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,

            modWhen, crc32, isBootstrap))

    {

        ALOGE("Optimization failed");

        goto bail;

    }

    /* we don't shut the VM down -- process is about to exit */

    result = 0;

bail:

    dexZipCloseArchive(&zippy);

    return result;

}


上面大概明白了具体的操作流程,下一步我们直接锁定到有关的一些步骤进行分析。
#创建空odex文件,以及写入0xff到头部信息结构中
libdex/OptInvocation.cpp->dexOptCreateEmptyHeader

int dexOptCreateEmptyHeader(int fd)

{

    DexOptHeader optHdr;

    ssize_t actual;

    assert(lseek(fd, 0, SEEK_CUR) == 0);

    /*

     * 创建一个odex(后面简称opt)的头信息,并全部填充0xFF

     */

    assert((sizeof(optHdr) & 0x07) == 0);

    memset(&optHdr, 0xff, sizeof(optHdr));

    //dex文件偏移量,从opt头信息后开始

    optHdr.dexOffset = sizeof(optHdr);

    actual = write(fd, &optHdr, sizeof(optHdr));

    if (actual != sizeof(optHdr)) {

        int err = errno ? errno : -1;

        ALOGE("opt header write failed: %s", strerror(errno));

        return errno;

    }

    return 0;

}


初始化vm根据dexopt工具选项值。设备VM不同,选项不同,所以才会出现不同设备间的odex不能通用的原因。
vm/Init.cpp->dvmPrepForDexOpt
...
执行优化,这就是关键函数了
vm/analysis/DexPrepare.cpp -> dvmContinueOptimization

bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,

    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)

{

    DexClassLookup* pClassLookup = NULL;//类查询对象

    RegisterMapBuilder* pRegMapBuilder = NULL;//映射表构建对象

    assert(gDvm.optimizing);

    ALOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap);

    assert(dexOffset >= 0);

    /* 验证头不大小是否符合规格,以及dex偏移量是否正确 */

    if (dexLength < (int) sizeof(DexHeader)) {

        ALOGE("too small to be DEX");

        return false;

    }

    if (dexOffset < (int) sizeof(DexOptHeader)) {

        ALOGE("not enough room for opt header");

        return false;

    }

    bool result = false;

    /*

     * Drop this into a global so we don't have to pass it around.  We could

     * also add a field to DexFile, but since it only pertains to DEX

     * creation that probably doesn't make sense.

     */

    gDvm.optimizingBootstrapClass = isBootstrap;

    {

        /*

         * mmap映射dex到内存,并获取dex在map表的起始地址

         */

        bool success;

        void* mapAddr;

        mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,

                    MAP_SHARED, fd, 0);

        if (mapAddr == MAP_FAILED) {

            ALOGE("unable to mmap DEX cache: %s", strerror(errno));

            goto bail;

        }

	//读取gDvm的验证模式

        bool doVerify, doOpt;

        if (gDvm.classVerifyMode == VERIFY_MODE_NONE) {

            doVerify = false;

        } else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) {

            doVerify = !gDvm.optimizingBootstrapClass;

        } else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ {

            doVerify = true;

        }

	//读取gDvm的优化模式

        if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) {

            doOpt = false;

        } else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||

                   gDvm.dexOptMode == OPTIMIZE_MODE_FULL) {

            doOpt = doVerify;

        } else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ {

            doOpt = true;

        }

        /*

         * 重写文件.  字节重新排序, 结构重新调整, 类验证,字节码优化。有兴趣的朋友可以看看rewriteDex就在当前类,因为水太深了,篇章有限不想搞太复杂。

	 * 类优化就跟之前的dexopt优化选项有关,只要我们将jar和apk有关验证的信息节点弄清楚了,就可以放入手机进行优化。

	 * 记得原数据不管jar,apk,odex都很重要,里面都潜藏着我们需要修复的信息节点。

	 * 其次,该方法中还能学到如何映射内存,修改映射空间,以及回写到磁盘等操作。

         */

        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,

                    doVerify, doOpt, &pClassLookup, NULL);

        if (success) {

            DvmDex* pDvmDex = NULL;//虚拟的dex对象

            u1* dexAddr = ((u1*) mapAddr) + dexOffset;//因为优化过,所以mapAddr地址的dex实体偏移了dexOffset,这里得到的是实体dex偏移后的地址

            //打开优化后的dex,将解析的dex创建到DvmDex附加结构上,

            if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {

                ALOGE("Unable to create DexFile");

                success = false;

            } else {

                /*

                 * 如果该配置启用,将所有验证类输出map表。

                 */

                if (gDvm.generateRegisterMaps) {

                    pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);

                    if (pRegMapBuilder == NULL) {

                        ALOGE("Failed generating register maps");

                        success = false;

                    }

                }

		//更新dex校验和到pHeader

                DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;

                updateChecksum(dexAddr, dexLength, pHeader);

		//释放pDvmDex,完成虚拟dex步骤

                dvmDexFileFree(pDvmDex);

            }

        }

        /* 将mmap映射空间的修改写回到磁盘文件(mmap中的参数fd就是磁盘文件) */

        if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) {

            ALOGW("msync failed: %s", strerror(errno));

            // weird, but keep going

        }

#if 1

        /*

         * 清理映射空间

         */

        if (munmap(mapAddr, dexOffset + dexLength) != 0) {

            ALOGE("munmap failed: %s", strerror(errno));

            goto bail;

        }

#endif

        if (!success)

            goto bail;

    }

    /* 获取odex包的各种偏移量起点 */

    off_t depsOffset, optOffset, endOffset, adjOffset;

    int depsLength, optLength;

    u4 optChecksum;

    depsOffset = lseek(fd, 0, SEEK_END);

    if (depsOffset < 0) {

        ALOGE("lseek to EOF failed: %s", strerror(errno));

        goto bail;

    }

    adjOffset = (depsOffset + 7) & ~(0x07);

    if (adjOffset != depsOffset) {

        ALOGV("Adjusting deps start from %d to %d",

            (int) depsOffset, (int) adjOffset);

        depsOffset = adjOffset;

        lseek(fd, depsOffset, SEEK_SET);

    }

    /*

     * 追加依赖文件,这个方法必须好好读一遍,刚好是上一段文里,关于验证odex相关数据段的,正操作。还是把代码附加在下面给大家看吧。

     */

    if (writeDependencies(fd, modWhen, crc) != 0) {

        ALOGW("Failed writing dependencies");

        goto bail;

    }

    /* 计算deps长度开始64位对齐 */

    optOffset = lseek(fd, 0, SEEK_END);

    depsLength = optOffset - depsOffset;

    adjOffset = (optOffset + 7) & ~(0x07);

    if (adjOffset != optOffset) {

        ALOGV("Adjusting opt start from %d to %d",

            (int) optOffset, (int) adjOffset);

        optOffset = adjOffset;

        lseek(fd, optOffset, SEEK_SET);

    }

    /*

     * 追加任意的优化数据结构

     */

    if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) {

        ALOGW("Failed writing opt data");

        goto bail;

    }

    endOffset = lseek(fd, 0, SEEK_END);

    optLength = endOffset - optOffset;

    /* 计算deps的总校验和,这个节点在上面验证过程中貌似没看到过。 */

    if (!computeFileChecksum(fd, depsOffset,

            (optOffset+optLength) - depsOffset, &optChecksum))

    {

        goto bail;

    }

    /*

     * 重新计算了一下odex头的相关信息

     */

    DexOptHeader optHdr;

    memset(&optHdr, 0xff, sizeof(optHdr));

    memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);

    memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);

    optHdr.dexOffset = (u4) dexOffset;

    optHdr.dexLength = (u4) dexLength;

    optHdr.depsOffset = (u4) depsOffset;

    optHdr.depsLength = (u4) depsLength;

    optHdr.optOffset = (u4) optOffset;

    optHdr.optLength = (u4) optLength;

#if __BYTE_ORDER != __LITTLE_ENDIAN

    optHdr.flags = DEX_OPT_FLAG_BIG;

#else

    optHdr.flags = 0;

#endif

    optHdr.checksum = optChecksum;

    fsync(fd);      /* 同步内存中已修改的文件到存储设备,这里的fd只是磁盘文件的内存指针,上面有过一次回写到磁盘文件,但是并未关闭文件指针 */

    lseek(fd, 0, SEEK_SET);

    // 调整指针,将头部信息写入文件

    if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0)

        goto bail;

    ALOGV("Successfully wrote DEX header");

    result = true;

    //dvmRegisterMapDumpStats();

bail:

    dvmFreeRegisterMapBuilder(pRegMapBuilder);

    free(pClassLookup);

    return result;

}


追加依赖文件函数:writeDependencies 此代码必看,看过后就知道每个dep项的数据结构是如何的。解析deps和修复它就不是问题了。

static int writeDependencies(int fd, u4 modWhen, u4 crc)

{

    u1* buf = NULL;

    int result = -1;

    ssize_t bufLen;

    ClassPathEntry* cpe;

    int numDeps;

    /*

     * 计算bootclasspath依赖文件的条目数 numDeps

     */

    numDeps = 0;

    bufLen = 0;

    for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {

        const char* cacheFileName =

            dvmPathToAbsolutePortion(getCacheFileName(cpe));

        assert(cacheFileName != NULL); /* guaranteed by Class.c */

        ALOGV("+++ DexOpt: found dep '%s'", cacheFileName);

        numDeps++;

        bufLen += strlen(cacheFileName) +1;

    }

    //缓冲大小计算

    bufLen += 4*4 + numDeps * (4+kSHA1DigestLen);

    //申请对应的缓冲区

    buf = (u1*)malloc(bufLen);

    //字节高低位转换

    set4LE(buf+0, modWhen);

    set4LE(buf+4, crc);

    set4LE(buf+8, DALVIK_VM_BUILD);

    set4LE(buf+12, numDeps);

    // 循环处理bootclasspath依赖文件

    u1* ptr = buf + 4*4;

    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);

	// 计算文件大小

        int len = strlen(cacheFileName) +1;

        if (ptr + 4 + len + kSHA1DigestLen > buf + bufLen) {

            ALOGE("DexOpt: overran buffer");

            dvmAbort();

        }

        // 追加文件名称和签名到odex deps列表

        set4LE(ptr, len);

        ptr += 4;

        memcpy(ptr, cacheFileName, len);

        ptr += len;

        memcpy(ptr, signature, kSHA1DigestLen);

        ptr += kSHA1DigestLen;

    }

    assert(ptr == buf + bufLen);

    result = sysWriteFully(fd, buf, bufLen, "DexOpt dep info");

    free(buf);

    return result;

}


终于写完了。希望能对大家有所帮助!!

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功