dalvik 虚拟机启动入口在 JNI_CreateJavaVM(), 在进行完 JNIEnv 等环境设置后, 调用 dvmStartup() 函数进行真正的 DVM 初始化.
- jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
- --snip--
- std::string status = dvmStartup(argc, argv.get(),
- args->ignoreUnrecognized, (JNIEnv*)pEnv);
- --snip--
- }
dvmStartup() 进行了一系列 DVM 子模块初始化工作, 主要关注 dvmInternalNativeStartup() , 这个函数用来初始化一个内部 Native 函数集.
- std::string dvmStartup(int argc, const char* const argv[],
- bool ignoreUnrecognized, JNIEnv* pEnv)
- {
- --snip--
- if (!dvmInlineNativeStartup()) {
- return "dvmInlineNativeStartup";
- }
- --snip--
- }
所有需要直接访问 Dalvik 虚拟机内部函数或者数据结构的 Native 函数都需要定义在这个集合中, dvmInlineNativeStartup() 创建了函数集的 HashTable 用于快速访问.
- bool dvmInternalNativeStartup()
- {
- DalvikNativeClass* classPtr = gDvmNativeMethodSet;
- --snip--
- gDvm.userDexFiles = dvmHashTableCreate(2, dvmFreeDexOrJar);
- --snip--
- }
查看 gDvmNativeMethodSet 变量定义:
- static DalvikNativeClass gDvmNativeMethodSet[] = {
- --snip--
- { "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 },
- --snip--
- };
所有与 DEX 文件操作相关的 Native 函数都定义在 dvm_dalvik_system_DexFile() 中
- const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
- { "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
- Dalvik_dalvik_system_DexFile_openDexFileNative },
- { "openDexFile", "([B)I",
- Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
- { "closeDexFile", "(I)V",
- Dalvik_dalvik_system_DexFile_closeDexFile },
- { "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
- Dalvik_dalvik_system_DexFile_defineClassNative },
- { "getClassNameList", "(I)[Ljava/lang/String;",
- Dalvik_dalvik_system_DexFile_getClassNameList },
- { "isDexOptNeeded", "(Ljava/lang/String;)Z",
- Dalvik_dalvik_system_DexFile_isDexOptNeeded },
- { NULL, NULL, NULL },
- };
openDexFileNative 与 openDexFile 函用于打开一个 DEX 文件, 区别是 openDexFile 是 Opening in-memory DEX, 而 openDexFileNative 读取的是磁盘文件.
Dalvik_dalvik_system_DexFile_openDexFileNative() 函数用于打开 jar 或 DEX 文件.
这里的 jar 也可以是 APK 文件, 即用户安装的应用, 他们本质都是 zip 压缩包.
DEX 文件走 dvmRawDexFileOpen() 函数分支.
jar 文件走 dvmJarFileOpen() 函数分支, 这是最常见的方式.
- --snip--
- } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
- ALOGV("Opening DEX file'%s'(Jar)", sourceName);
- pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
- pDexOrJar->isDex = false;
- pDexOrJar->pJarFile = pJarFile;
- pDexOrJar->pDexMemory = NULL;
- } else {
- --snip--
dvmJarFileOpen() 执行成功后, 在 pJarFile 中保存了 zip 文件, dex(实际为 opt 过的 odex 文件) 文件加载到内存的地址:
- struct JarFile {
- ZipArchive archive;
- //MemMapping map;
- char* cacheFileName;
- DvmDex* pDvmDex;
- };
继续看 dvmJarFileOpen() 函数实现:
- int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
- JarFile** ppJarFile, bool isBootstrap)
- {
- /* 将 zip 读入内存 archive 中 */
- if (dexZipOpenArchive(fileName, &archive) != 0)
- goto bail;
- fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
- if (fd>= 0) {
- /* 如果缓存中存在 fileName.odex 文件 */
- if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) { /* 检查如果不是新的, 跳转到 tryArchive */
- ALOGE("%s odex has stale dependencies", fileName);
- goto tryArchive;
- } else {
- ALOGV("%s odex has good dependencies", fileName);
- }
- } else {
- ZipEntry entry;
- tryArchive:
- /* 查找 zip 中的 DEX 文件 */
- entry = dexZipFindEntry(&archive, kDexInJarName);
- if (entry != NULL) {
- /* 优化为 odex */
- dvmOptimizeDexFile(...);
- }
- }
- /* 将 odex 文件映射到内存 pDvmDex */
- if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
- ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
- goto bail;
- }
- /* 初始化 pJarFile 结构体 */
- (*ppJarFile)->archive = archive;
- (*ppJarFile)->cacheFileName = cachedName;
- (*ppJarFile)->pDvmDex = pDvmDex;
- }
DEX 在内存加载完成后, 挂载到 HashTable 上:
addToDexFileTable(pDexOrJar);
dvmOptimizeDexFile() 是一个重要的函数, 它通过 fork 执行 /bin/dexopt 程序进行 DEX 优化操作. 代码在 OptMain.cpp 中
dvmOptimizeDexFile() 设置了 "-dex" 参数, 它决定了 dexopt 函数中的分支:
- argv[curArg++] = "--dex";
- int main(int argc, char* const argv[])
- {
- set_process_name("dexopt");
- --snip--
- if (argc> 1) {
- if (strcmp(argv[1], "--zip") == 0)
- return fromZip(argc, argv);
- else if (strcmp(argv[1], "--dex") == 0)
- return fromDex(argc, argv);
- else if (strcmp(argv[1], "--preopt") == 0)
- return preopt(argc, argv);
- }
- --snip--
- }
-dex 走 fromDex() 分支, 主要优化在 dvmContinueOptimization() 函数完成:
- /* do the optimization */
- if (!dvmContinueOptimization(fd, offset, length, debugFileName,
- modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
- {
- ALOGE("Optimization failed");
- goto bail;
- }
- bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
- const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
- {
- --snip--
- /* 映射整个文件到内存 */
- void* mapAddr;
- 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;
- }
- /* rewriteDex 主要做对齐, 大小端转换等操作 */
- success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, doVerify, doOpt, &pClassLookup, NULL);
- u1* dexAddr = ((u1*) mapAddr) + dexOffset;
- /* 经常在这个里下断或 Hook 脱壳, 因为完成了 rewriteDex
- 完成由 DEX File 到内存 pDvmDex 结构的映射
- */
- if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
- ALOGE("Unable to create DexFile");
- success = false;
- }
- /* 最后生成 odex 文件 */
- --snip--
- }
- int dvmDexFileOpenPartial (const void* addr, int len, DvmDex** ppDvmDex)
- {
- DvmDex* pDvmDex;
- DexFile* pDexFile;
- --snip--
- pDexFile = dexFileParse((u1*)addr, len, parseFlags);
- pDvmDex = allocateAuxStructures(pDexFile);
- pDvmDex->isMappedReadOnly = false;
- *ppDvmDex = pDvmDex;
- --snip--
- }
- /*
- * Parse an optimized or unoptimized .dex file sitting in memory. This is
- * called after the byte-ordering and structure alignment has been fixed up.
- *
- * On success, return a newly-allocated DexFile.
- */
- DexFile* dexFileParse(const u1* data, size_t length, int flags)
疑问:
1. 为什么没有直接 opt 前 dump 的文章? 为什么要在 rewriteDex 后, Hook 验证之
来源: http://www.bubuko.com/infodetail-2824401.html