文章目录
前言
一、DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析
二、/bin/dexopt 源码分析
前言
上一篇博客 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 ) 中 , 在 RawDexFile.cpp 中的 dvmRawDexFileOpen() 方法中 , 调用了 DexPrepare.cpp 的 dvmOptimizeDexFile() 函数 , 对 DEX 文件进行了优化 ;
一、DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析
dvmOptimizeDexFile 函数的参数说明 : int fd 是打开的 dex 文件标识符 , long dexLength 是打开的 dex 文件大小 ;
在该函数中 , 调用 /bin/dexopt 程序 , 优化 dex 文件 , 最终产生 odex 文件 ;
/* * 给定包含DEX数据的文件的描述符,生成 * 优化版本。 * * “fd”指向的文件应为锁定的共享资源 * (或私人);我们不努力实施多进程正确性 * 在这里。 * * “文件名”仅用于调试输出。存储“modWhen”和“crc” * 在依赖项集中。 * * “isBootstrap”标志确定优化器和验证器如何处理 * 包范围访问检查。优化时,我们只加载引导 * 类DEX文件和目标DEX,因此该标志确定 * 给目标DEX类一个(合成的)非空类加载器指针。 * 只有当目标DEX包含声明 * 与引导类位于同一个包中。 * * 优化器需要加载目标DEX文件中的每个类。 * 这通常是不可取的,因此我们启动一个子流程来执行 * 工作并等待它完成。 * * 成功时返回“true”。所有数据均已写入“fd”。 */ bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength, const char* fileName, u4 modWhen, u4 crc, bool isBootstrap) { const char* lastPart = strrchr(fileName, '/'); if (lastPart != NULL) lastPart++; else lastPart = fileName; ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap); pid_t pid; /* * 如果我们的bootclasspath中出现了我们认为 * 都优化了,被拒绝了。 */ if (gDvm.optimizing) { ALOGW("Rejecting recursive optimization attempt on '%s'", fileName); return false; } pid = fork(); if (pid == 0) { static const int kUseValgrind = 0; // 调用 /bin/dexopt 程序 , 优化 dex 文件 , 最终产生 odex 文件 static const char* kDexOptBin = "/bin/dexopt"; static const char* kValgrinder = "/usr/bin/valgrind"; static const int kFixedArgCount = 10; static const int kValgrindArgCount = 5; static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig int bcpSize = dvmGetBootPathSize(); int argc = kFixedArgCount + bcpSize + (kValgrindArgCount * kUseValgrind); const char* argv[argc+1]; // last entry is NULL char values[argc][kMaxIntLen]; char* execFile; const char* androidRoot; int flags; /* change process groups, so we don't clash with ProcessManager */ setpgid(0, 0); /* full path to optimizer */ androidRoot = getenv("ANDROID_ROOT"); if (androidRoot == NULL) { ALOGW("ANDROID_ROOT not set, defaulting to /system"); androidRoot = "/system"; } execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1); strcpy(execFile, androidRoot); strcat(execFile, kDexOptBin); /* * Create arg vector. */ int curArg = 0; if (kUseValgrind) { /* probably shouldn't ship the hard-coded path */ argv[curArg++] = (char*)kValgrinder; argv[curArg++] = "--tool=memcheck"; argv[curArg++] = "--leak-check=yes"; // check for leaks too argv[curArg++] = "--leak-resolution=med"; // increase from 2 to 4 argv[curArg++] = "--num-callers=16"; // default is 12 assert(curArg == kValgrindArgCount); } argv[curArg++] = execFile; argv[curArg++] = "--dex"; sprintf(values[2], "%d", DALVIK_VM_BUILD); argv[curArg++] = values[2]; sprintf(values[3], "%d", fd); argv[curArg++] = values[3]; sprintf(values[4], "%d", (int) dexOffset); argv[curArg++] = values[4]; sprintf(values[5], "%d", (int) dexLength); argv[curArg++] = values[5]; argv[curArg++] = (char*)fileName; sprintf(values[7], "%d", (int) modWhen); argv[curArg++] = values[7]; sprintf(values[8], "%d", (int) crc); argv[curArg++] = values[8]; flags = 0; if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) { flags |= DEXOPT_OPT_ENABLED; if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL) flags |= DEXOPT_OPT_ALL; } if (gDvm.classVerifyMode != VERIFY_MODE_NONE) { flags |= DEXOPT_VERIFY_ENABLED; if (gDvm.classVerifyMode == VERIFY_MODE_ALL) flags |= DEXOPT_VERIFY_ALL; } if (isBootstrap) flags |= DEXOPT_IS_BOOTSTRAP; if (gDvm.generateRegisterMaps) flags |= DEXOPT_GEN_REGISTER_MAPS; sprintf(values[9], "%d", flags); argv[curArg++] = values[9]; assert(((!kUseValgrind && curArg == kFixedArgCount) || ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount)))); ClassPathEntry* cpe; for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) { argv[curArg++] = cpe->fileName; } assert(curArg == argc); argv[curArg] = NULL; if (kUseValgrind) execv(kValgrinder, const_cast<char**>(argv)); else execv(execFile, const_cast<char**>(argv)); ALOGE("execv '%s'%s failed: %s", execFile, kUseValgrind ? " [valgrind]" : "", strerror(errno)); exit(1); } else { ALOGV("DexOpt: waiting for verify+opt, pid=%d", (int) pid); int status; pid_t gotPid; /* * 等待优化过程完成。我们进入VMI等待 * 模式,这样GC暂停就不必等待我们了。 */ ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); while (true) { gotPid = waitpid(pid, &status, 0); if (gotPid == -1 && errno == EINTR) { ALOGD("waitpid interrupted, retrying"); } else { break; } } dvmChangeStatus(NULL, oldStatus); if (gotPid != pid) { ALOGE("waitpid failed: wanted %d, got %d: %s", (int) pid, (int) gotPid, strerror(errno)); return false; } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { ALOGD("DexOpt: --- END '%s' (success) ---", lastPart); return true; } else { ALOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed", lastPart, status); return false; } } }
二、/bin/dexopt 源码分析
dex 文件优化 , 主要是调用 /bin/dexopt 程序 , 最终产生 odex 文件 ;
其源码路径是 /dalvik/dexopt/ 路径 ,
该 OptMain.cpp 源码是一个有 main 函数 , 可以独立执行的 C++ 程序 , 可以在 Android 命令中执行 ;
加载 dex 文件时 , 执行 fromDex 函数 ;
return fromDex(argc, argv);
在 fromfromDex 函数中 , 先准备优化环境 ,
if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) { ALOGE("VM init failed"); goto bail; }
然后进行正式优化 ;
/* do the optimization */ if (!dvmContinueOptimization(fd, offset, length, debugFileName, modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0)) { ALOGE("Optimization failed"); goto bail; }
真正的优化操作 , 在 dvmContinueOptimization 函数中执行的 ;
核心源码如下 : 源码路径 /dalvik/dexopt/OptMain.cpp
/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * 命令行DEX优化和验证入口点。 * * 有三种方法可以启动此功能: * (1)来自虚拟机。这需要十几个参数,其中一个是文件 * 同时作为输入和输出的描述符。这使我们能够 * 仍然不知道DEX数据最初来自何处。 * (2)来自installd或其他本机应用程序。传入文件 * 用于zip文件的描述符、用于输出的文件描述符,以及 * 调试消息的文件名。关于这一点,人们做了许多假设 * 发生了什么(验证+优化已启用,启动 * 类路径位于BOOTCLASSPATH中,等等)。 * (3)在构建过程中在主机上进行预优化。这种行为 * 与(2)几乎相同,只是它采用文件名而不是 * 文件描述符。 * * bootclasspath条目存在一些脆弱的方面,原因如下 * 很大程度上是由于虚拟机在它认为需要的时候进行工作的历史 * 而不是严格按照要求去做。如果优化引导类路径 * 条目,始终按照它们在路径中出现的顺序执行。 */ #include "Dalvik.h" #include "libdex/OptInvocation.h" #include "cutils/log.h" #include "cutils/process_name.h" #include <fcntl.h> #include <stdbool.h> #include <stdlib.h> #include <stdio.h> #include <string.h> static const char* kClassesDex = "classes.dex"; /* *将zipFd中的“classes.dex”提取到“cacheFd”中,留下一点空间 *用于DEX优化收割台的前端。 */ static int extractAndProcessZip(int zipFd, int cacheFd, const char* debugFileName, bool isBootstrap, const char* bootClassPath, const char* dexoptFlagStr) { ZipArchive zippy; ZipEntry zipEntry; size_t uncompLen; long modWhen, crc32; off_t dexOffset; int err; int result = -1; int dexoptFlags = 0; /* bit flags, from enum DexoptFlags */ DexClassVerifyMode verifyMode = VERIFY_MODE_ALL; DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED; memset(&zippy, 0, sizeof(zippy)); /* make sure we're still at the start of an empty file */ if (lseek(cacheFd, 0, SEEK_END) != 0) { ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName); goto bail; } /* *编写骨架索引优化标头。我们要上课。指数 *紧跟其后。 */ err = dexOptCreateEmptyHeader(cacheFd); if (err != 0) goto bail; /* record the file position so we can get back here later */ dexOffset = lseek(cacheFd, 0, SEEK_CUR); if (dexOffset < 0) goto bail; /* *打开zip存档,找到DEX条目。 */ if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) { ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName); goto bail; } zipEntry = dexZipFindEntry(&zippy, kClassesDex); if (zipEntry == NULL) { ALOGW("DexOptZ: zip archive '%s' does not include %s", debugFileName, kClassesDex); goto bail; } /* *提取一些关于zip条目的信息。 */ if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL, &modWhen, &crc32) != 0) { ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName); goto bail; } uncompLen = uncompLen; modWhen = modWhen; crc32 = crc32; /* *以当前偏移量将DEX数据提取到缓存文件中。 */ if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) { ALOGW("DexOptZ: extraction of %s from %s failed", kClassesDex, debugFileName); goto bail; } /* Parse the options. */ if (dexoptFlagStr[0] != '\0') { const char* opc; const char* val; opc = strstr(dexoptFlagStr, "v="); /* verification */ if (opc != NULL) { switch (*(opc+2)) { case 'n': verifyMode = VERIFY_MODE_NONE; break; case 'r': verifyMode = VERIFY_MODE_REMOTE; break; case 'a': verifyMode = VERIFY_MODE_ALL; break; default: break; } } opc = strstr(dexoptFlagStr, "o="); /* optimization */ if (opc != NULL) { switch (*(opc+2)) { case 'n': dexOptMode = OPTIMIZE_MODE_NONE; break; case 'v': dexOptMode = OPTIMIZE_MODE_VERIFIED; break; case 'a': dexOptMode = OPTIMIZE_MODE_ALL; break; case 'f': dexOptMode = OPTIMIZE_MODE_FULL; break; default: break; } } opc = strstr(dexoptFlagStr, "m=y"); /* register map */ if (opc != NULL) { dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS; } opc = strstr(dexoptFlagStr, "u="); /* uniprocessor target */ if (opc != NULL) { switch (*(opc+2)) { case 'y': dexoptFlags |= DEXOPT_UNIPROCESSOR; break; case 'n': dexoptFlags |= DEXOPT_SMP; break; default: break; } } } /* *准备VM并执行优化。 */ if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, dexoptFlags) != 0) { ALOGE("DexOptZ: VM init failed"); goto bail; } //vmStarted = 1; /* do the optimization */ if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName, modWhen, crc32, isBootstrap)) { ALOGE("Optimization failed"); goto bail; } /* we don't shut the VM down -- process is about to exit */ result = 0; bail: dexZipCloseArchive(&zippy); return result; } /* *普通设备端处理的通用功能以及 *预优化。 */ static int processZipFile(int zipFd, int cacheFd, const char* zipName, const char *dexoptFlags) { char* bcpCopy = NULL; /* * Check to see if this is a bootstrap class entry. If so, truncate * the path. */ const char* bcp = getenv("BOOTCLASSPATH"); if (bcp == NULL) { ALOGE("DexOptZ: BOOTCLASSPATH not set"); return -1; } bool isBootstrap = false; const char* match = strstr(bcp, zipName); if (match != NULL) { /* *TODO:我们有一个部分字符串匹配,但这并不意味着 *我们已经匹配了整个路径组件。我们应该确保 *我们正在匹配完整的zipName,如果不是 *应从(匹配+1)开始重新执行strstr。 * *该场景将是一个bootclasspath,具有以下内容 *“/system/framework/core.jar”,而我们正在尝试优化 *“/framework/core.jar”。不太可能,因为所有路径都是 *绝对,以“.jar”结尾,但并非不可能。 */ int matchOffset = match - bcp; if (matchOffset > 0 && bcp[matchOffset-1] == ':') matchOffset--; ALOGV("DexOptZ: found '%s' in bootclasspath, cutting off at %d", zipName, matchOffset); bcpCopy = strdup(bcp); bcpCopy[matchOffset] = '\0'; bcp = bcpCopy; ALOGD("DexOptZ: truncated BOOTCLASSPATH to '%s'", bcp); isBootstrap = true; } int result = extractAndProcessZip(zipFd, cacheFd, zipName, isBootstrap, bcp, dexoptFlags); free(bcpCopy); return result; } /* advance to the next arg and extract it */ #define GET_ARG(_var, _func, _msg) \ { \ char* endp; \ (_var) = _func(*++argv, &endp, 0); \ if (*endp != '\0') { \ ALOGE("%s '%s'", _msg, *argv); \ goto bail; \ } \ --argc; \ } /* *解析参数。我们希望: * 0. (dexopt命令的名称--已忽略) * 1. “--zip” * 2. zip fd(输入,只读) * 3. 缓存fd(输出、读写、用群集锁定) * 4. 正在优化的zipfile的文件名(用于调试消息和 *用于与BOOTCLASSPATH进行比较;不需要 *可访问或甚至存在) * 5. dexopt标志 * *假定BOOTCLASSPATH环境变量包含正确的 *引导类路径。如果提供的文件名出现在引导类中 *路径,路径将在该条目之前被截断(因此,如果 *如果您选择dexopt“core.jar”,您的引导类路径将为空)。 * *这不会尝试规范化引导类路径名,因此 *如果你有创意,文件名测试不会抓住你。 */ static int fromZip(int argc, char* const argv[]) { int result = -1; int zipFd, cacheFd; const char* zipName; char* bcpCopy = NULL; const char* dexoptFlags; if (argc != 6) { ALOGE("Wrong number of args for --zip (found %d)", argc); goto bail; } /* skip "--zip" */ argc--; argv++; GET_ARG(zipFd, strtol, "bad zip fd"); GET_ARG(cacheFd, strtol, "bad cache fd"); zipName = *++argv; --argc; dexoptFlags = *++argv; --argc; result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags); bail: return result; } /* *分析预优化运行的参数。这是dalvikvm运行的时间 *在主机上优化dex文件,以便最终在主机上运行(不同) *装置。我们希望: * 0. (dexopt命令的名称--已忽略) * 1. “--preopt” * 2. zipfile名称 * 3. 输出文件名 * 4. dexopt标志 * *假定BOOTCLASSPATH环境变量包含正确的 *引导类路径。如果提供的文件名出现在引导类中 *路径,路径将在该条目之前被截断(因此,如果 *如果您选择dexopt“core.jar”,您的引导类路径将为空)。 * *这不会尝试规范化引导类路径名,因此 *如果你有创意,文件名测试不会抓住你。 */ static int preopt(int argc, char* const argv[]) { int zipFd = -1; int outFd = -1; int result = -1; if (argc != 5) { /* * Use stderr here, since this variant is meant to be called on * the host side. */ fprintf(stderr, "Wrong number of args for --preopt (found %d)\n", argc); return -1; } const char* zipName = argv[2]; const char* outName = argv[3]; const char* dexoptFlags = argv[4]; if (strstr(dexoptFlags, "u=y") == NULL && strstr(dexoptFlags, "u=n") == NULL) { fprintf(stderr, "Either 'u=y' or 'u=n' must be specified\n"); return -1; } zipFd = open(zipName, O_RDONLY); if (zipFd < 0) { perror(argv[0]); return -1; } outFd = open(outName, O_RDWR | O_EXCL | O_CREAT, 0666); if (outFd < 0) { perror(argv[0]); goto bail; } result = processZipFile(zipFd, outFd, zipName, dexoptFlags); bail: if (zipFd >= 0) { close(zipFd); } if (outFd >= 0) { close(outFd); } return result; } /* *直接从VM解析“旧式”调用的参数。 * *以下是我们想要的: * 0. (dexopt命令的名称--已忽略) * 1. “--dex” * 2. DALVIK_VM_构建值,作为一种健全性检查 * 3. 文件描述符,用flock锁定,用于正在优化的DEX文件 * 4. 文件内的索引偏移量 * 5. 指数长度 * 6. 正在优化的文件的文件名(仅适用于调试消息) * 7. 源的修改日期(进入依赖项部分) * 8. 源的CRC(进入依赖项部分) * 9. 标志(优化级别,isBootstrap) * 10. bootclasspath条目#1 * 11. bootclasspath条目#2 * ... * *dalvik/vm/analysis/DexOptimize中的dvmOptimizeDexFile()。c构建 *参数列表并调用此可执行文件。 * *bootclasspath条目将成为此DEX文件的依赖项。 * *打开的文件描述符不能用于任何bootclasspath文件。 *父项已锁定描述符,我们将尝试再次将其锁定 *处理引导类路径的一部分。(我们可以抓住这个然后回来 *比较文件名或打开bootclasspath文件时出错 *并统计它们的索引节点编号)。 */ static int fromDex(int argc, char* const argv[]) { int result = -1; bool vmStarted = false; char* bootClassPath = NULL; int fd, flags, vmBuildVersion; long offset, length; const char* debugFileName; u4 crc, modWhen; char* endp; bool onlyOptVerifiedDex = false; DexClassVerifyMode verifyMode; DexOptimizerMode dexOptMode; if (argc < 10) { /* don't have all mandatory args */ ALOGE("Not enough arguments for --dex (found %d)", argc); goto bail; } /* skip "--dex" */ argc--; argv++; /* * Extract the args. */ GET_ARG(vmBuildVersion, strtol, "bad vm build"); if (vmBuildVersion != DALVIK_VM_BUILD) { ALOGE("DexOpt: build rev does not match VM: %d vs %d", vmBuildVersion, DALVIK_VM_BUILD); goto bail; } GET_ARG(fd, strtol, "bad fd"); GET_ARG(offset, strtol, "bad offset"); GET_ARG(length, strtol, "bad length"); debugFileName = *++argv; --argc; GET_ARG(modWhen, strtoul, "bad modWhen"); GET_ARG(crc, strtoul, "bad crc"); GET_ARG(flags, strtol, "bad flags"); ALOGV("Args: fd=%d off=%ld len=%ld name='%s' mod=%#x crc=%#x flg=%d (argc=%d)", fd, offset, length, debugFileName, modWhen, crc, flags, argc); assert(argc > 0); if (--argc == 0) { bootClassPath = strdup(""); } else { int i, bcpLen; char* const* argp; char* cp; bcpLen = 0; for (i = 0, argp = argv; i < argc; i++) { ++argp; ALOGV("DEP: '%s'", *argp); bcpLen += strlen(*argp) + 1; } cp = bootClassPath = (char*) malloc(bcpLen +1); for (i = 0, argp = argv; i < argc; i++) { int strLen; ++argp; strLen = strlen(*argp); if (i != 0) *cp++ = ':'; memcpy(cp, *argp, strLen); cp += strLen; } *cp = '\0'; assert((int) strlen(bootClassPath) == bcpLen-1); } ALOGV(" bootclasspath is '%s'", bootClassPath); /* start the VM partway */ /* ugh -- upgrade these to a bit field if they get any more complex */ if ((flags & DEXOPT_VERIFY_ENABLED) != 0) { if ((flags & DEXOPT_VERIFY_ALL) != 0) verifyMode = VERIFY_MODE_ALL; else verifyMode = VERIFY_MODE_REMOTE; } else { verifyMode = VERIFY_MODE_NONE; } if ((flags & DEXOPT_OPT_ENABLED) != 0) { if ((flags & DEXOPT_OPT_ALL) != 0) dexOptMode = OPTIMIZE_MODE_ALL; else dexOptMode = OPTIMIZE_MODE_VERIFIED; } else { dexOptMode = OPTIMIZE_MODE_NONE; } // 准备优化环境 if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) { ALOGE("VM init failed"); goto bail; } vmStarted = true; /* 正式进行优化 */ if (!dvmContinueOptimization(fd, offset, length, debugFileName, modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0)) { ALOGE("Optimization failed"); goto bail; } result = 0; bail: /* *理论上,此时我们应该优雅地关闭VM。在里面 *只有当我们使用检查内存泄漏时,这才有意义 *valgrind——简单地退出要快得多。 * *事实证明,DEX优化器有点快,有点松 *使用类加载。我们从一个部分- *形成的DEX文件,完成后将取消映射。如果我们想 *在这里进行清洁关机,可能是为了使用valgrind进行测试,我们需要 *要跳过那里的munmap调用。 */ #if 0 if (vmStarted) { ALOGI("DexOpt shutting down, result=%d", result); dvmShutdown(); } #endif free(bootClassPath); ALOGV("DexOpt command complete (result=%d)", result); return result; } /* *主要入口点。决定去哪里。 */ int main(int argc, char* const argv[]) { set_process_name("dexopt"); setvbuf(stdout, NULL, _IONBF, 0); if (argc > 1) { if (strcmp(argv[1], "--zip") == 0) return fromZip(argc, argv); else if (strcmp(argv[1], "--dex") == 0) // 加载 dex 文件时 , 执行 fromDex 函数 return fromDex(argc, argv); else if (strcmp(argv[1], "--preopt") == 0) return preopt(argc, argv); } fprintf(stderr, "Usage:\n\n" "Short version: Don't use this.\n\n" "Slightly longer version: This system-internal tool is used to\n" "produce optimized dex files. See the source code for details.\n"); return 1; }