前两天有事情耽搁了。今天开始分析dexdump代码。代码位与android-4.0.1_r1/dalvik/dexdump中里面只有一个CPP文件。通过文件包含可以看出。主要的一些代码都在libdex目录实现。这个库也在dalvik目录中与dexdump目录同级。
以下是头包含文件。
#include "libdex/DexFile.h"
#include "libdex/CmdUtils.h"
#include "libdex/DexCatch.h"
#include "libdex/DexClass.h"
#include "libdex/DexDebugInfo.h"
#include "libdex/DexOpcodes.h"
#include "libdex/DexProto.h"
#include "libdex/InstrUtils.h"
#include "libdex/SysUtil.h"
我准备从main开始以此对每个调用的函数进行分析。
/*
* Parse args.
*
* I'm not using getopt_long() because we may not have it in libc.
* 从以上注释可以看出NDK没有实现getopt_long函数
*/
int main(int argc, char* const argv[])
{
bool wantUsage = false;
int ic;
// 初始化命令行参数结构
memset(&gOptions, 0, sizeof(gOptions));
gOptions.verbose = true;
while (1) {
ic = getopt(argc, argv, "cdfhil:mt:");
if (ic < 0)
break;
switch (ic) {
case 'c': // 检验文件并且退出
gOptions.checksumOnly = true;
break;
case 'd': // 反汇编
gOptions.disassemble = true;
break;
case 'f': // 打印出文件头
gOptions.showFileHeaders = true;
break;
case 'h': // 打印出每个节头的信息
gOptions.showSectionHeaders = true;
break;
case 'i': // 忽略校验和
gOptions.ignoreBadChecksum = true;
break;
case 'l': // 输出模式
if (strcmp(optarg, "plain") == 0) {
gOptions.outputFormat = OUTPUT_PLAIN;
} else if (strcmp(optarg, "xml") == 0) {
gOptions.outputFormat = OUTPUT_XML;
gOptions.verbose = false;
gOptions.exportsOnly = true;
} else {
wantUsage = true;
}
break;
case 'm': // 打印寄存器图
gOptions.dumpRegisterMaps = true;
break;
case 't': // 在APK解压时的临时文件
gOptions.tempFileName = optarg;
break;
default:
wantUsage = true;
break;
}
}
if (optind == argc) {
fprintf(stderr, "%s: no file specified\n", gProgName);
wantUsage = true;
}
// -c 与 -i 两个选项不能同时使用
if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) {
fprintf(stderr, "Can't specify both -c and -i\n");
wantUsage = true;
}
if (wantUsage) {
usage();
return 2;
}
// 这里是一个循环,看来可以同时处理多个dex文件。
int result = 0;
while (optind < argc) {
result |= process(argv[optind++]);
}
return (result != 0);
}
main函数也就做了一个命令行分析而已。最后使用process函数对每个文件进行处理。
/*
* Process one file.
* 主要的处理函数
*/
int process(const char* fileName)
{
// DexFile文件结构
DexFile* pDexFile = NULL;
MemMapping map;
bool mapped = false;
int result = -1;
if (gOptions.verbose)
printf("Processing '%s'...\n", fileName);
// 映射dex文件并将映射结果存入MemMapping结构中。
if (dexOpenAndMap(fileName, gOptions.tempFileName, &map, false) != 0) {
return result;
}
mapped = true;
// 这里按照标志选项是否忽略校验和检查
int flags = kDexParseVerifyChecksum;
if (gOptions.ignoreBadChecksum)
flags |= kDexParseContinueOnError;
// 这个函数是开始分析DEX文件结构,并返回一个DexFile的指针
pDexFile = dexFileParse((u1*)map.addr, map.length, flags);
if (pDexFile == NULL) {
fprintf(stderr, "ERROR: DEX parse failed\n");
goto bail;
}
// 如果以上分析完成,说明校验和没问题,这里如果设置了-c选项则直接打印后退出
if (gOptions.checksumOnly) {
printf("Checksum verified\n");
} else {
// 这里应该是开始按照选项输出pDexFile的结果
processDexFile(fileName, pDexFile);
}
result = 0;
// 释放资源
bail:
if (mapped)
sysReleaseShmem(&map);
if (pDexFile != NULL)
dexFileFree(pDexFile);
return result;
}
从以上代码来看主要有两个函数和一个结构需要重点分析,一个是dexOpenAndMap另一个更加重要是dexFileParse。一个结构是struct DexFile结构。这两个函数和结构都位于libdex中。首先来看下DexFile结构长的是什么样子。
struct DexFile {
/* directly-mapped "opt" header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
// 映射Dex整体文件
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
// 一些附加的节段信息
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
// 指向Dex文件开始
const u1* baseAddr;
/* track memory overhead for auxillary structures */
// 附属的内存数据
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
};
以上就是DexFile结构,可以看出它开始保存了文件头以及若干个数据目录的指针。并且还有一些其他附属数据的指针。dexOpenAndMap就是要将一个DEX文件映射到这个结构中。
dexOpenAndMap位于libdex\CmdUtils.cpp文件中,这个函数的流程如下:
1.判断文件后缀名是否是dex如果不是则尝试解压,解压失败则退出。解压成功则取出其中的classes.dex文件。
2.打开dex文件
3.使用位于libdex\SysUtils.cpp文件中的sysMapFileInShmemWritableReadOnly函数进行映射
4.使用libdex\SysUtils.cpp中的sysChangeMapAccess先将映射修改为可读可写
5.使用libdex\DexSwapVerify.cpp中的dexSwapAndVerifyIfNecessary函数进行验证
6.sysChangeMapAccess修改映射到只可写
7.退出
函数代码如
UnzipToFileResult dexOpenAndMap(const char* fileName, const char* tempFileName,
MemMapping* pMap, bool quiet)
{
UnzipToFileResult result = kUTFRGenericFailure;
int len = strlen(fileName);//计算文件名长度
char tempNameBuf[32];
bool removeTemp = false;
int fd = -1;
if (len < 5) {
// 如果没有设定安静执行则输出
if (!quiet) {
fprintf(stderr,
"ERROR: filename must end in .dex, .zip, .jar, or .apk\n");
}
result = kUTFRBadArgs;
goto bail;
}
// 判断后缀是否是dex
if (strcasecmp(fileName + len -3, "dex") != 0) {
if (tempFileName == NULL) {
/*
* Try .zip/.jar/.apk, all of which are Zip archives with
* "classes.dex" inside. We need to extract the compressed
* data to a temp file, the location of which varies.
*
* On the device we must use /sdcard because most other
* directories aren't writable (either because of permissions
* or because the volume is mounted read-only). On desktop
* it's nice to use the designated temp directory.
*/
// 尝试解压,获取解压的临时文件
if (access("/tmp", W_OK) == 0) {
sprintf(tempNameBuf, "/tmp/dex-temp-%d", getpid());
} else if (access("/sdcard", W_OK) == 0) {
sprintf(tempNameBuf, "/sdcard/dex-temp-%d", getpid());
} else {
fprintf(stderr,
"NOTE: /tmp and /sdcard unavailable for temp files\n");
sprintf(tempNameBuf, "dex-temp-%d", getpid());
}
// 解压的目录
tempFileName = tempNameBuf;
}
// 解压文件
result = dexUnzipToFile(fileName, tempFileName, quiet);
if (result == kUTFRSuccess) {
//printf("+++ Good unzip to '%s'\n", tempFileName);
fileName = tempFileName;//重新设定文件名
removeTemp = true;
} else if (result == kUTFRNotZip) {// 不是一个压缩包
if (!quiet) {
fprintf(stderr, "Not Zip, retrying as DEX\n");
}
} else {// 压缩包内没有dex文件
if (!quiet && result == kUTFRNoClassesDex) {
fprintf(stderr, "Zip has no classes.dex\n");
}
goto bail;
}
}
result = kUTFRGenericFailure;
/*
* Pop open the (presumed) DEX file.
*/
// 打开DEX文件
fd = open(fileName, O_RDONLY | O_BINARY);
if (fd < 0) {
if (!quiet) {
fprintf(stderr, "ERROR: unable to open '%s': %s\n",
fileName, strerror(errno));
}
goto bail;
}
// 映射dex文件到一个仅读内存,以下函数在SysUntil.cpp中进行实现
if (sysMapFileInShmemWritableReadOnly(fd, pMap) != 0) {
fprintf(stderr, "ERROR: Unable to map '%s'\n", fileName);
goto bail;
}
/*
* This call will fail if the file exists on a filesystem that
* doesn't support mprotect(). If that's the case, then the file
* will have already been mapped private-writable by the previous
* call, so we don't need to do anything special if this call
* returns non-zero.
*
* 当前函数调用失败,如果是因为文件系统不支持mprotect函数。如果是这种情况,此文件已经有
* 可以被所有者写入的权限,因此不需要为这个调用返回任何特殊的标记。
*/
// 设置访问权限为可读可写
sysChangeMapAccess(pMap->addr, pMap->length, true, pMap);
// 验证签名
if (dexSwapAndVerifyIfNecessary((u1*) pMap->addr, pMap->length)) {
fprintf(stderr, "ERROR: Failed structural verification of '%s'\n",
fileName);
goto bail;
}
/*
* Similar to above, this call will fail if the file wasn't ever
* read-only to begin with. This is innocuous, though it is
* undesirable from a memory hygiene perspective.
*/
// 重新设定访问权限
sysChangeMapAccess(pMap->addr, pMap->length, false, pMap);
/*
* Success! Close the file and return with the start/length in pMap.
*/
result = kUTFRSuccess;
bail:
if (fd >= 0)
close(fd);
if (removeTemp) {
/* this will fail if the OS doesn't allow removal of a mapped file */
if (unlink(tempFileName) != 0) {
fprintf(stderr, "WARNING: unable to remove temp '%s'\n",
tempFileName);
}
}
return result;
}z和
这里可以看出主要的验证在libdex\DexSwapVerify.cpp中的dexSwapAndVerifyIfNecessary中进行验证。在第三篇在分析这个验证的过程。
转载于:https://blog.51cto.com/devilogic/1201650