开始分析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本身信息的指针验证。