您的位置:首页 > 其它

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

2013-05-16 14:49 381 查看
开始分析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本身信息的指针验证。

本文出自 “西西东东与南南” 博客,请务必保留此出处http://devilogic.blog.51cto.com/7117723/1201800
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: