• Dalvik源码阅读笔记(一)


    dalvik 虚拟机启动入口在 JNI_CreateJavaVM(), 在进行完 JNIEnv 等环境设置后,调用 dvmStartup() 函数进行真正的 DVM 初始化。

    jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
        --snip--
        std::string status = dvmStartup(argc, argv.get(),
            args->ignoreUnrecognized, (JNIEnv*)pEnv);
        --snip--
    }
    

    dvmStartup() 进行了一系列 DVM 子模块初始化工作,主要关注 dvmInternalNativeStartup() ,这个函数用来初始化一个内部 Native 函数集。

    std::string dvmStartup(int argc, const char* const argv[],
            bool ignoreUnrecognized, JNIEnv* pEnv)
    {
        --snip--
        if (!dvmInlineNativeStartup()) {
            return "dvmInlineNativeStartup";
        }
        --snip--
    }
    

    所有需要直接访问 Dalvik 虚拟机内部函数或者数据结构的 Native 函数都需要定义在这个集合中,dvmInlineNativeStartup() 创建了函数集的 HashTable 用于快速访问。

    bool dvmInternalNativeStartup()
    {
        DalvikNativeClass* classPtr = gDvmNativeMethodSet;
        --snip--
        gDvm.userDexFiles = dvmHashTableCreate(2, dvmFreeDexOrJar);
        --snip--
    }
    

    查看 gDvmNativeMethodSet 变量定义:

    static DalvikNativeClass gDvmNativeMethodSet[] = {
        --snip--
        { "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 },
        --snip--
    };
    

    所有与 DEX 文件操作相关的 Native 函数都定义在 dvm_dalvik_system_DexFile() 中

    const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
        { "openDexFileNative",  "(Ljava/lang/String;Ljava/lang/String;I)I",
            Dalvik_dalvik_system_DexFile_openDexFileNative },
        { "openDexFile",        "([B)I",
            Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
        { "closeDexFile",       "(I)V",
            Dalvik_dalvik_system_DexFile_closeDexFile },
        { "defineClassNative",  "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
            Dalvik_dalvik_system_DexFile_defineClassNative },
        { "getClassNameList",   "(I)[Ljava/lang/String;",
            Dalvik_dalvik_system_DexFile_getClassNameList },
        { "isDexOptNeeded",     "(Ljava/lang/String;)Z",
            Dalvik_dalvik_system_DexFile_isDexOptNeeded },
        { NULL, NULL, NULL },
    };
    

    openDexFileNative 与 openDexFile 函用于打开一个 DEX 文件,区别是 openDexFile 是 Opening in-memory DEX,而 openDexFileNative 读取的是磁盘文件。

    Dalvik_dalvik_system_DexFile_openDexFileNative() 函数用于打开 jar 或 DEX 文件。

    这里的 jar 也可以是 APK 文件,即用户安装的应用,他们本质都是 zip 压缩包。

    DEX 文件走 dvmRawDexFileOpen() 函数分支。

    jar 文件走 dvmJarFileOpen() 函数分支,这是最常见的方式。

    --snip--
        } 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 {
        --snip--
    

    dvmJarFileOpen() 执行成功后,在 pJarFile 中保存了 zip 文件,dex(实际为 opt 过的 odex 文件) 文件加载到内存的地址:

    struct JarFile {
        ZipArchive  archive;
        //MemMapping  map;
        char*       cacheFileName;
        DvmDex*     pDvmDex;
    };
    

    继续看 dvmJarFileOpen() 函数实现:

    int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
        JarFile** ppJarFile, bool isBootstrap)
    {
        /* 将 zip 读入内存 archive 中 */
        if (dexZipOpenArchive(fileName, &archive) != 0) 
            goto bail;
            
        fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
        if (fd >= 0) {
            /* 如果缓存中存在 fileName.odex 文件 */
            if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) { /* 检查如果不是新的,跳转到 tryArchive */
                ALOGE("%s odex has stale dependencies", fileName);
                goto tryArchive;
            } else {
                ALOGV("%s odex has good dependencies", fileName);
            }
        } else { 
            ZipEntry entry;
    tryArchive:
            /* 查找 zip 中的 DEX 文件 */
            entry = dexZipFindEntry(&archive, kDexInJarName);
            if (entry != NULL) {
                /* 优化为 odex */
                dvmOptimizeDexFile(...);
            }
        }
        /* 将 odex 文件映射到内存 pDvmDex */
        if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {  
            ALOGI("Unable to map %s in %s", kDexInJarName, fileName); 
            goto bail;
        }
        /* 初始化 pJarFile 结构体 */
        (*ppJarFile)->archive = archive;
        (*ppJarFile)->cacheFileName = cachedName;
        (*ppJarFile)->pDvmDex = pDvmDex;
    }
    

    DEX 在内存加载完成后,挂载到 HashTable 上:

    addToDexFileTable(pDexOrJar);
    

    dvmOptimizeDexFile() 是一个重要的函数,它通过 fork 执行 /bin/dexopt 程序进行 DEX 优化操作。代码在 OptMain.cpp 中

    dvmOptimizeDexFile() 设置了“–dex”参数,它决定了 dexopt 函数中的分支:

    argv[curArg++] = "--dex";
    
    int main(int argc, char* const argv[])
    {
        set_process_name("dexopt");
    --snip--
        if (argc > 1) {
            if (strcmp(argv[1], "--zip") == 0)
                return fromZip(argc, argv);
            else if (strcmp(argv[1], "--dex") == 0)
                return fromDex(argc, argv);
            else if (strcmp(argv[1], "--preopt") == 0)
                return preopt(argc, argv);
        }
        --snip--
    }
    

    –dex 走 fromDex() 分支,主要优化在 dvmContinueOptimization() 函数完成:

    /* do the optimization */
    if (!dvmContinueOptimization(fd, offset, length, debugFileName,
            modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
    {
        ALOGE("Optimization failed");
        goto bail;
    }
    
    bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
        const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
    {
    --snip--
        /* 映射整个文件到内存 */
        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;
        }
        
            
        /* rewriteDex 主要做对齐,大小端转换等操作 */
        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, doVerify, doOpt, &pClassLookup, NULL);
        
        u1* dexAddr = ((u1*) mapAddr) + dexOffset;
        /* 经常在这个里下断或Hook脱壳,因为完成了rewriteDex
            完成由 DEX File 到内存 pDvmDex 结构的映射
        */
        if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
            ALOGE("Unable to create DexFile");
            success = false;
        }
        
        /* 最后生成 odex 文件 */
        --snip--
    }
    
    int dvmDexFileOpenPartial (const void* addr, int len, DvmDex** ppDvmDex)
    {
        DvmDex* pDvmDex;
        DexFile* pDexFile;
    --snip--
        pDexFile = dexFileParse((u1*)addr, len, parseFlags);
        
        pDvmDex = allocateAuxStructures(pDexFile);
        pDvmDex->isMappedReadOnly = false;
        *ppDvmDex = pDvmDex;
    --snip--
    }
    
    /*
     * Parse an optimized or unoptimized .dex file sitting in memory.  This is
     * called after the byte-ordering and structure alignment has been fixed up.
     *
     * On success, return a newly-allocated DexFile.
     */
    DexFile* dexFileParse(const u1* data, size_t length, int flags)
    

    疑问:
    1.为什么没有直接opt前dump的文章?为什么要在rewriteDex后,Hook 验证之

  • 相关阅读:
    Pthon魔术方法(Magic Methods)-可视化
    Pthon魔术方法(Magic Methods)-实例化
    Java(Java SE7) 体系结构图
    利用环境变量打造自己的工具
    lua table排序报错与解决
    .gho文件检查
    文件服务器HFS
    在Unity中对Lua进行调试
    Lua不显示小数点0的部分
    Lua保留指定小数位数
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864046.html
Copyright © 2020-2023  润新知