网络安全防护与全方位灾备 7月03日 星期四 15:00 云祺视频号准时直播
云祺直播二维码
扫码预约直播,观看精彩内容! 扫码预约直播,观看精彩内容!
关闭按钮
云祺Logo 云祺Logo
关于我们

技术分享

技术分享 Android 应用程序安装原理之虚拟机加载详解

Android 应用程序安装原理之虚拟机加载详解

2019-07-18

上一篇文章分享了Android应用程序的安装原理,让大家对应用程序安装过程从源代码的角度有个全面的了解。但是,应用程序安装后,在设备中具体的存储形式是什么?应用程序加载到系统虚拟机(本文只涉及Dalvik虚拟机,暂不考虑ART)中又是如何加载运行的呢?这就是接下来我们将要分享的内容。

用户程序安装完成后,将会在用户设备的/data/app目录保存应用程序文件,格式为data/app/包名-1.apk,例如:/data/app/com.carrieriq.trial-1.apk,如图1所示:

Android 应用程序安装原理之虚拟机加载详解

图1 应用程序安装后保存目录
同时,用户程序的内部数据会在/data/data/包名目录下保存,格式为/data/data/应用程序包名,如: /data/data/com.example.hellojni。注意lib/so文件会被重定向到data/app-lib目录,也就是应用程序中的so文件被放在了data/app-lib目录,格式为/data/app-lib/包名/xxx.so,如图2所示:

Android 应用程序安装原理之虚拟机加载详解

图2 应用程序中的so文件保存目录

到了这里,我们知道了应用程序是如何安装的,安装后是如何存储的,那又是如何运行的呢?Dalvik虚拟机加载的是classes.dex文件,而APK文件是ZIP压缩格式,Dalvik虚拟机每次加载classes.dex文件,都要从APK文件中获取,这样会消耗很多CPU的时间,为了节省时间,应用程序安装后会在/data/dalvik-cache/目录下生成apk对应的优化文件,文件格式为odex,是classes.dex的优化后的文件。用户安装的保存在data/app目录下apk包名-1.apk程序对应的优化文件(odex格式),保存形式为data@app@apk包名-1.apk@classes.dex,这样应用程序再启动时就直接从/data/dalvik-cache目录下读取优化过的文件,节省时间,如图3所示:

Android 应用程序安装原理之虚拟机加载详解

图3 应用程序优化文件odex的保存目录

那应用程序中dex文件,odex文件在系统的虚拟机中又是如何加载运行的呢?
由于篇幅原因,我们将从三个方面来技术分享,第一方面是dex文件结构如何优化成odex文件结构,第二方面是odex文件结构如何解析成dexfile结构,第三方面是dexfile文件结构是如何转化虚拟机需要的运行时的数据结构ClassObject*字节码,供解释器执行。
dex文件解析的整体框图如图4所示:

Android 应用程序安装原理之虚拟机加载详解

图4 dex文件解析整体框图
本篇文章将基于Android4.4.2的源代码,从源代码的角度进行分析dex文件结构如何优化成odex文件结构。

1. dex文件结构优化成odex文件结构

Android提供了一个将dex文件结构转换成odex文件结构的工具——dexopt(位于/system/bin/dexopt),它的源代码位于 Dalvik/dexopt/Optmain.cpp文件中。其中,extractAndProcessZip()函数用于处理并优化apk/jar/zip文件中的classes.dex文件,因此我们从该函数开始分析dex文件的优化过程。
static const char* kClassesDex = "classes.dex";static int extractAndProcessZip(int zipFd, int cacheFd,const char* debugFileName, bool isBootstrap, const char* bootClassPath,const char* dexoptFlagStr){    ZipArchive zippy;      /*描述zip压缩文件的数据结构 */    ZipEntry zipEntry;     /*表示一个zip入口 */    size_t uncompLen;      /* dex文件优化前的文件长度 */    long modWhen, crc32;   /* dex优化前的时间戳和crc校验值 */    off_t dexOffset;       /* dex文件的起始偏移 */    int err;                  int result = -1;            int dexoptFlags = 0;    /*优化标识符 */    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;    /*取得odex文件中原dex文件的起始偏移,实际上是odex文件头部的长度,即开始+0x28字节 */    dexOffset = lseek(cacheFd, 0, SEEK_CUR);     if(dexOffset < 0)        goto bail;    if(dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);        goto bail;    }/*获取目标classes.dex文件的解压入口 */    zipEntry = dexZipFindEntry(&zippy, kClassesDex);       if(zipEntry == NULL){        ALOGW("DexOptZ: zip archive '%s' does not include %s",            debugFileName, kClassesDex);        goto bail;    }    if(dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,            &modWhen, &crc32) != 0)    {    ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);        goto bail;    }    uncompLen = uncompLen; /*dex文件优化前的长度 */    modWhen = modWhen;    /*dex文件优化前的时间戳 */    crc32 = crc32;        /*dex文件优化前crc32值 */    /*将解压出的classes.dex文件写入odex文件,cacheFd指向odex文件,文件中已包含odex的头部信息 */    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=");    /* verification */        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=");    /* optimization */        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");  /* register map */        if (opc != NULL) {            dexoptFlags |= dexOPT_GEN_REGISTER_MAPS;        }        opc = strstr(dexoptFlagStr, "u="); /* uniprocessor target */        if (opc != NULL) {            switch (*(opc+2)) {            case 'y': dexoptFlags |= dexOPT_UNIPROCESSOR;  break;            case 'n': dexoptFlags |= dexOPT_SMP;           break;            default:                                       break;            }        }    }    /*初始化虚拟机专用于优化工作 */    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,            dexoptFlags) != 0)    {        ALOGE("DexOptZ:VM init failed");        goto bail;    }    /*完成对dex文件的验证和优化 */    if(!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,modWhen, crc32,  isBootstrap))    {        ALOGE("Optimization failed");          goto bail;    }    result = 0;   /*成功 */bail:    dexZipCloseArchive(&zippy);return result; }
下面继续跟踪dalvik/vm/analysis/DexPrepare.cpp文件中的dvmContinueOptimization()函数。
/*fd:odex文件,包含了odex的头部信息和dex文件信息,dexOffset:dex文件的偏移地址,dexLength:dex文件的长度*/bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap){    /*dex类索引哈希表结构 */    DexClassLookup* pClassLookup = NULL;     RegisterMapBuilder* pRegMapBuilder = NULL;    assert(gDvm.optimizing);    ALOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap);    assert(dexOffset >= 0);  /*判断Odex头长度是否为0 */    /*校验dex文件,长度不能小于其文件头的长度 */    if(dexLength < (int)sizeof(DexHeader)){        ALOGE("too small to be dex");        return false;    }    /*校验dex文件的起始偏移量,不能小于odex文件头的长度 */    if (dexOffset < (int) sizeof(DexOptHeader)) {        ALOGE("not enough room for opt header");        return false;    }    bool result = false;    gDvm.optimizingBootstrapClass = isBootstrap;    {        bool success;        void* mapAddr;  /*内存映射起始位置 */        /*将fd所指的文件映射到某一位置,该位置的起始位置为mapAddr,大小为dexOffset + dexLength */        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;        }        ...        /*对dex文件进行验证、重写、字符调整、字节码替换等 */        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,                    doVerify, doOpt, &pClassLookup, NULL);        if (success) {            /*虚拟机解析的Dex文件结构指针 */            DvmDex* pDvmDex = NULL;             u1* dexAddr = ((u1*) mapAddr) + dexOffset;           /*调用此函数创建一个DexFile文件结构,dexAddr为dex文件的起始偏移,dexLength为dex文件的长度 */           if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {                ALOGE("Unable to create DexFile");                success = false;            } else {                if (gDvm.generateRegisterMaps) {               /*生成映射池 */                pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);                    if (pRegMapBuilder == NULL) {                        ALOGE("Failed generating register maps");                        success = false;                    }                }                /*获取dex文件头部 */                DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;                 /*更新dex文件的crc32校验值 */                updateChecksum(dexAddr, dexLength, pHeader);                       dvmDexFileFree(pDvmDex);            }        }    ...    /*准备写入前,先进行8字节文件对齐处理 */    off_t depsOffset, optOffset, endOffset, adjOffset;    int depsLength, optLength;    u4 optChecksum;    depsOffset=lseek(fd, 0, SEEK_END);/*获取当前fd所指文件的总长 */    if(depsOffset < 0){        ALOGE("lseek to EOF failed: %s", strerror(errno));        goto bail;    }    /*使depsOffset(dependency的起始地址 )8字节对齐,且adjOffset >= depsOffset */    adjOffset = (depsOffset + 7) & ~(0x07);     if (adjOffset != depsOffset) {        ALOGV("Adjusting deps start from %d to %d",            (int) depsOffset, (int) adjOffset);        depsOffset = adjOffset;  /*odex文件依赖库列表偏移 */        lseek(fd, depsOffset, SEEK_SET);    }    /*写入依赖库信息,fd为Odex头+dex文件,modWhen为dex文件优化前时间戳,crc为dex文件优化前的crc32值 */    if (writeDependencies(fd, modWhen, crc) != 0) {        ALOGW("Failed writing dependencies");        goto bail;    }    optOffset = lseek(fd, 0, SEEK_END);   depsLength = optOffset - depsOffset; /*依赖库dependency总长度 */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;  /*优化数据的总长度 */    /*计算依赖库和优化数据总长的sum值 */    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);  /*odex版本标识 */    memcpy(optHdr.magic+4, dex_OPT_MAGIC_VERS, 4);     optHdr.dexOffset = (u4)dexOffset;   /*dex文件头偏移 */    optHdr.dexLength = (u4)dexLength;   /*dex文件总长度 */    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);         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;}
根据上面的源代码可知,我们首先会创建一个空的Odex文件,接着调用dexOptCreateEmpty Header()函数为odex文件创建一个空的DexOptHeader头,再调用dexZipExtractEntryToFile()函数将提取的classes.dex文件写入odex文件;然后调用dvmPrepForDexOpt()函数启动并初始化一个虚拟机进程,调用dvmContinueOptimization()函数对dex文件进行优化和验证,在该函数内调用mmap()函数将原来的dex文件映射到内存,调用rewriteDex()函数对dex文件进行重写,重写的内容有字节码替换、字节码验证、文件结构重新对齐等,再调用writeDependencies()和writeOptData()函数写入依赖库和赋值信息;最后根据最终的偏移和长度重新修正odex文件头部,输出优化完成的odex文件。
dex文件优化成odex文件结构的整体流向图,如图5所示:

Android 应用程序安装原理之虚拟机加载详解

图5 dex文件优化成odex文件结构的整体流向图
注意,图5中包含了很多校验过程,为了简洁,图中并未全部展示,具体以源代码为主。当虚拟机优化完成后,在Dalvik虚拟机缓存中生成dex文件对应的odex文件,下一篇将分享优化过后的dex文件是如何解析成DexFile数据结构。
 




云祺备份软件,云祺容灾备份系统,虚拟机备份,数据库备份,文件备份,实时备份,勒索软件,美国,图书馆
  • 标签:
  • 技术分享

您可能感兴趣的新闻 换一批

现在下载,可享30天免费试用

立即下载

请添加好友为您提供支持
jia7jia_7

请拨打电话
为您提供支持

400-9955-698