.dex文件结构学习笔记(3)

开始分析dexSwapAndVerifyIfNecessary函数。这个函数位于libdex/DexSwapVerify.cpp中。这个文件主要负责转换DEX文件中的大/小字节序,并且验证文件的校验和。

代码如下:


int dexSwapAndVerifyIfNecessary(u1* addr, int len)
{
    if (memcmp(addr, DEX_OPT_MAGIC, 4) == 0) {
        // It is an optimized dex file.
        return 0;
    }
    if (memcmp(addr, DEX_MAGIC, 4) == 0) {
        // It is an unoptimized dex file.
        return dexSwapAndVerify(addr, len);
    }
    LOGE("ERROR: Bad magic number (0x%02x %02x %02x %02x)",
             addr[0], addr[1], addr[2], addr[3]);
    return 1;
}

从这里看出首先先校验了DEX的MAGIC标记如果是一个优化后的DEX文件则不需要校验直接退出。

如果没有进行过优化并且MAGIC字段正确,则直接调用dexSwapAndVerify进行验证。

dexSwapAndVerify的大体流程如下:

1.MAGIC与DEX文件版本匹配

2.获取DEX文件长度

3.从Dex头中取出checksum字段(此字段是校验和)

4.计算DEX文件校验和,计算校验和时忽略掉magic与checksum因为这两个字段是变动的,不能算作DEX文件固定内容。使用Adler32算法。

5.匹配校验和

6.调用swapDexHeader设置文件头字段的字节序。

7.检查文件头长度字段是否合法。

8.检测映射数据偏移是否为空,不为空则进行映射并检验其代码如下:

if (pHeader->mapOff != 0) {
    DexFile dexFile;
    DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff);
    okay = okay && swapMap(&state, pDexMap);
    okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    // 建立完整的DEX文件头
    dexFileSetupBasicPointers(&dexFile, addr);
    state.pDexFile = &dexFile;
    okay = okay && crossVerifyEverything(&state, pDexMap);
} else {
    LOGE("ERROR: No map found; impossible to byte-swap and verify");
    okay = false;
}

mapOff字段究竟是干什么用的。这里判断了一下它是否为0,也就是说它有可能为0,有可能不为0。我用grep搜索了一下libdex目录看了下多少地方来引用它。结果只有DexSwapVerify.cpp中使用,并且只限于验证功能。用dexdump打印了一下第一篇文章中的android程序发现mapOff字段并没有打印出来。看来它的作用不大。但是我在这里也去分析一下验证它的流程。它的第二个参数是一个DexMapList的指针。这个地址指向了Dex文件头 +mapOff的地址。这个结构如下:

struct DexMapList{
    u4 size;// DexMapItem的个数
    DexMapItem list[1];
};
struct DexMapItem {
    u2 type;
    u2 unused;
    u4 size;
    u4 offset;
};

这两个结构在第一篇文章的提到过。当时我以为是文件映射到内存时保存数据目录的内存格式。看来只猜对了一半。通过这里来看它是一开始就存在于文件上了。并没有在加载DEX时才按照DexMapItem的格式映射上去。这个结构除了unused没有用处外,第一个字段表示item的类型,size表示这样类型的item有几个,offset表示相对于文件开始地址的偏移。


进入第一个调用函数swapMap,从此函数的名称看来它的作用是确定字节序。它遍历了整个DexMapList,并且确定每个DexMapItem字段的字节序。并且验证每个item的偏移值是否合法

从代码可以看出,如果一个Item项是属于数据节的类型则将它的计数从整体Item计数总减掉。确定

Item是否属于数据段调用isDataSectionType函数。代码如下:

static bool isDataSectionType(int mapType) {
    switch (mapType) {
        case kDexTypeHeaderItem:
        case kDexTypeStringIdItem:
        case kDexTypeTypeIdItem:
        case kDexTypeProtoIdItem:
        case kDexTypeFieldIdItem:
        case kDexTypeMethodIdItem:
        case kDexTypeClassDefItem: {
            return false;
        }
    }
    return true;
}

从以上函数看来,dex文件把kDexTypeHeaderItem,kDexTypeStringIdItem,kDexTypeTypeIdItem,kDexTypeProtoIdItem,kDexTypeFieldIdItem,kDexTypeMethodIdItem,kDexTypeClassDefItem这样几个类型作为数据。

在遍历item完毕之后,则验证一些必须存在的数据节如果不存在以下几个数据节则失败。

kDexTypeHeaderItem,kDexTypeMapList,kDexTypeStringIdItem,kDexTypeTypeIdItem,kDexTypeProtoIdItem,kDexTypeFieldIdItem,kDexTypeMethodIdItem,kDexTypeClassDefItem

最后调用dexDataMapAlloc分配数据映射内存。

第二个调用的函数是swapEverythingButHeaderAndMap,从名称看来这个函数用来确定其他item的字节序,除了文件头与map节,如果遇到这两个节时,就做一些其余额外的合法性检查。如果遇到文件头则调用checkHeaderSection,如果遇到checkBoundsAndIterateSectionap节则调用checkMapSection。其余则调用checkBoundsAndIterateSection。

checkHeaderSection检查item的个数是否是1以及偏移是否是0,文件头的偏移自然是0。

checkMapSection检测item的偏移个数是否是1以及偏移是否与DexMap的偏移一致。

checkBoundsAndIterateSection(CheckState* state, u4 0ffset, u4 count, u4 expectedOffset, u4 expectedCount, ItemVisitorFunction* func, u4 alignment, u4* nextOffset);的原型如上。

第一个参数是state这个指针是验证文件时用的结构。第二个Offset是item的文件偏移,第三个参数是count是item的计数,第四个参数是expectedOffset是预期的偏移,直接从文件头中取得。在此函数内查看它是否与第二个参数相等。第五个参数是一个函数原型指针用来遍历不同的item节,这些回调函数可以通过查看swapEverythingButHeaderAndMap代码得到。第六个参数是对齐粒度,第七个参数是此item的末尾相当于文件开头的偏移。

如果是属于数据item例如字符串数据,调试信息数据则使用iterateDataSection来做检查。


第三个函数调用的是dexFileSetupBasicPointers,这个函数负责建立一个完整的DexFile结构,其代码如下所示:

void dexFileSetupBasicPointers(DexFile* pDexFile, const u1* data) {
    DexHeader *pHeader = (DexHeader*) data;
    pDexFile->baseAddr = data;
    pDexFile->pHeader = pHeader;
    pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff);
    pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff);
    pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff);
    pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff);
    pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff);
    pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff);
    pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff);
}

最后一个调用的函数是crossVerifyEverything,交叉验证所有的item,此函数实现如下:

static bool crossVerifyEverything(CheckState* state, DexMapList* pMap)
{
    const DexMapItem* item = pMap->list;
    u4 count = pMap->size;
    bool okay = true;
    // 遍历所有item
    while (okay && count--) {
        u4 sectionOffset = item->offset;
        u4 sectionCount = item->size;
        switch (item->type) {
            case kDexTypeHeaderItem:
            case kDexTypeMapList:
            case kDexTypeTypeList:
            case kDexTypeCodeItem:
            case kDexTypeStringDataItem:
            case kDexTypeDebugInfoItem:
            case kDexTypeAnnotationItem:
            case kDexTypeEncodedArrayItem: {
                // There is no need for cross-item verification for these.
                // 不需要交叉验证
                break;
            }
            case kDexTypeStringIdItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyStringIdItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeTypeIdItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyTypeIdItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeProtoIdItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyProtoIdItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeFieldIdItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyFieldIdItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeMethodIdItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyMethodIdItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeClassDefItem: {
                // Allocate (on the stack) the "observed class_def" bits.
                size_t arraySize = calcDefinedClassBitsSize(state);
                u4 definedClassBits[arraySize];
                memset(definedClassBits, 0, arraySize * sizeof(u4));
                state->pDefinedClassBits = definedClassBits;
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyClassDefItem, sizeof(u4), NULL);
                state->pDefinedClassBits = NULL;
                break;
            }
            case kDexTypeAnnotationSetRefList: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyAnnotationSetRefList, sizeof(u4), NULL);
                break;
            }
            case kDexTypeAnnotationSetItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyAnnotationSetItem, sizeof(u4), NULL);
                break;
            }
            case kDexTypeClassDataItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyClassDataItem, sizeof(u1), NULL);
                break;
            }
            case kDexTypeAnnotationsDirectoryItem: {
                okay = iterateSection(state, sectionOffset, sectionCount,
                        crossVerifyAnnotationsDirectoryItem, sizeof(u4), NULL);
                break;
            }
            default: {
                LOGE("Unknown map item type %04x", item->type);
                return false;
            }
        }
        if (!okay) {
            LOGE("Cross-item verify of section type %04x failed",
                    item->type);
        }
        item++;
    }
    return okay;
}

从以上代码可以看出有些item不需要交叉验证。而有些item则需要,并且调用了iterateSection函数此函数会分别调用每个item的回调函数进行验证。这里回调比较繁多。按照每个item结构不同进行不同的合法性验证。这里先不做一一分析了。大多验证都和DEX文件结构有关系。google程序员们还是很靠谱的。

9.退出并返回结果。

以下是dexSwapAndVerify的函数代码

int dexSwapAndVerify(u1* addr, int len)
{
    DexHeader* pHeader;
    CheckState state;
    bool okay = true;
    memset(&state, 0, sizeof(state));
    LOGV("+++ swapping and verifying");
    /*
     * Note: The caller must have verified that "len" is at least as
     * large as a dex file header.
     */
    // 获取文件头,len参数最小与一个dex文件头一样大
    pHeader = (DexHeader*) addr;
    // 匹配版本
    if (!dexHasValidMagic(pHeader)) {
        okay = false;
    }
    // 取文件长度
    if (okay) {
        int expectedLen = (int) SWAP4(pHeader->fileSize);
        if (len < expectedLen) {
            LOGE("ERROR: Bad length: expected %d, got %d", expectedLen, len);
            okay = false;
        } else if (len != expectedLen) {
            LOGW("WARNING: Odd length: expected %d, got %d", expectedLen,
                    len);
            // keep going
        }
    }
    if (okay) {
        /*
         * Compute the adler32 checksum and compare it to what's stored in
         * the file.  This isn't free, but chances are good that we just
         * unpacked this from a jar file and have all of the pages sitting
         * in memory, so it's pretty quick.
         *
         * This might be a big-endian system, so we need to do this before
         * we byte-swap the header.
         *
         * 在计算校验和时不将它与magic算在里面。
         */
        // 初始化一个adler32数
        uLong adler = adler32(0L, Z_NULL, 0);
        const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum);
        u4 storedFileSize = SWAP4(pHeader->fileSize);
        u4 expectedChecksum = SWAP4(pHeader->checksum);
        // 计算校验和,忽略magic与checksum两个字段
        adler = adler32(adler, ((const u1*) pHeader) + nonSum,
                    storedFileSize - nonSum);
        // 校验和不相等
        if (adler != expectedChecksum) {
            LOGE("ERROR: bad checksum (%08lx, expected %08x)",
                adler, expectedChecksum);
            okay = false;
        }
    }
    if (okay) {
        state.fileStart = addr;
        state.fileEnd = addr + len;
        state.fileLen = len;
        state.pDexFile = NULL;
        state.pDataMap = NULL;
        state.pDefinedClassBits = NULL;
        state.previousItem = NULL;
        /*
         * Swap the header and check the contents.
         * 转换字节序
         */
        okay = swapDexHeader(&state, pHeader);
    }
    // 校验文件头的大小
    if (okay) {
        state.pHeader = pHeader;
        if (pHeader->headerSize < sizeof(DexHeader)) {
            LOGE("ERROR: Small header size %d, struct %d",
                    pHeader->headerSize, (int) sizeof(DexHeader));
            okay = false;
        } else if (pHeader->headerSize > sizeof(DexHeader)) {
            LOGW("WARNING: Large header size %d, struct %d",
                    pHeader->headerSize, (int) sizeof(DexHeader));
            // keep going?
        }
    }
    if (okay) {
        /*
         * Look for the map. Swap it and then use it to find and swap
         * everything else.
         *
         * 如果需要直接按照内存进行映射
         */
        if (pHeader->mapOff != 0) {
            DexFile dexFile;
            DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff);
            okay = okay && swapMap(&state, pDexMap);
            okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
            // 建立完整的DEX文件头
            dexFileSetupBasicPointers(&dexFile, addr);
            state.pDexFile = &dexFile;
            okay = okay && crossVerifyEverything(&state, pDexMap);
        } else {
            LOGE("ERROR: No map found; impossible to byte-swap and verify");
            okay = false;
        }
    }
    if (!okay) {
        LOGE("ERROR: Byte swap + verify failed");
    }
    if (state.pDataMap != NULL) {
        dexDataMapFree(state.pDataMap);
    }
    return !okay;       // 0 == success
}

验证的过程还是很繁琐的。文件校验和只是一个最简单的过程。复杂的还是对DEX本身信息的指针验证。

你可能感兴趣的:(android,dalvik)