Android dex加载过程分析
来源:互联网 发布:网络语言ph是什么意思 编辑:程序博客网 时间:2024/06/05 11:33
啊啊啊啊啊啊啊,开始吧。
ClassLoader有两种,PathClassLoader和DexClassLoader
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); }}
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); }}源码就这么点,我也是惊了个呆
不同之处就在于super()函数的第二个参数
上次说到安装阶段已经生成了一个优化后的dex,所以PathClassLoader当中就是null,DexClassLoader则需要自行生成一个优化文件。
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }BaseDexClassLoader的构造函数
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists()) { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); this.nativeLibraryDirectories = splitLibraryPath(libraryPath); }DexPathList的构造函数
先进行一些验证,然后关键是makeDexElements和splitLibraryPath函数
先看第一个makeDexElements
/** * Makes an array of dex/resource path elements, one per element of * the given array. */ private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory) { ArrayList<Element> elements = new ArrayList<Element>(); /* * Open all files and load the (direct or contained) dex files * up front. */ for (File file : files) { File zip = null; DexFile dex = null; String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException ex) { System.logE("Unable to load dex file: " + file, ex); } } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) || name.endsWith(ZIP_SUFFIX)) { zip = file; try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException ignored) { /* * IOException might get thrown "legitimately" by * the DexFile constructor if the zip file turns * out to be resource-only (that is, no * classes.dex file in it). Safe to just ignore * the exception here, and let dex == null. */ } } else if (file.isDirectory()) { // We support directories for looking up resources. // This is only useful for running libcore tests. elements.add(new Element(file, true, null, null)); } else { System.logW("Unknown file type for: " + file); } if ((zip != null) || (dex != null)) { elements.add(new Element(file, false, zip, dex)); } } return elements.toArray(new Element[elements.size()]); }注释说的很清楚,产生一个dex或者resource元素的数组
如果文件以.dex、.apk、.jar和.zip结尾,都会调用loadDexFile,区别在于局部变量zip的赋值(具体看代码),如果路径是一个目录,则直接添加到array
前四种情况,都会在调用完loadDexFile后再添加
这里还有个小问题,makeDexElements函数的第一个参数,在调用时为splitDexPath(dexPath)
splitDexPath函数如下
private static ArrayList<File> splitDexPath(String path) { return splitPaths(path, null, false); }splitPaths函数如下
private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; }最终的处理在splitAndAdd函数中
private static void splitAndAdd(String searchPath, boolean directoriesOnly, ArrayList<File> resultList) { if (searchPath == null) { return; } for (String path : searchPath.split(":")) { try { StructStat sb = Libcore.os.stat(path); if (!directoriesOnly || S_ISDIR(sb.st_mode)) { resultList.add(new File(path)); } } catch (ErrnoException ignored) { } } }回到主线任务,下面看loadDexFile函数
private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { if (optimizedDirectory == null) { return new DexFile(file); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0); } }还记得吗,由于PathClassLoader的optimizedDirectory为null,所以这里调用的是new DexFile(file)
自然DexClassLoader调用的就是DexFile.loadDex(file.getPath(), optimizedPath, 0);
先看loadDex
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { /* * TODO: we may want to cache previously-opened DexFile objects. * The cache would be synchronized with close(). This would help * us avoid mapping the same DEX more than once when an app * decided to open it multiple times. In practice this may not * be a real issue. */ return new DexFile(sourcePathName, outputPathName, flags); }其实也就是DexFile的构造函数
private DexFile(String sourceName, String outputName, int flags) throws IOException { if (outputName != null) { try { String parent = new File(outputName).getParent(); if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { throw new IllegalArgumentException("Optimized data directory " + parent + " is not owned by the current user. Shared storage cannot protect" + " your application from code injection attacks."); } } catch (ErrnoException ignored) { // assume we'll fail with a more contextual error later } } mCookie = openDexFile(sourceName, outputName, flags); mFileName = sourceName; guard.open("close"); //System.out.println("DEX FILE cookie is " + mCookie); }首先,如果是DexClassLoader,会先检查父目录的所有者。
无论是哪种ClassLoader,最终都会调用openDexFile
这是一个native函数,位于dalvik\vm\native\dalvik_system_DexFile.cpp
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { { "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I", Dalvik_dalvik_system_DexFile_openDexFile }, { "openDexFile", "([B)I", Dalvik_dalvik_system_DexFile_openDexFile_bytearray }, { "closeDexFile", "(I)V", Dalvik_dalvik_system_DexFile_closeDexFile }, { "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", Dalvik_dalvik_system_DexFile_defineClass }, { "getClassNameList", "(I)[Ljava/lang/String;", Dalvik_dalvik_system_DexFile_getClassNameList }, { "isDexOptNeeded", "(Ljava/lang/String;)Z", Dalvik_dalvik_system_DexFile_isDexOptNeeded }, { NULL, NULL, NULL },};根据这个二维字符串数组,找到openDexFile所对应的函数
要注意函数参数,别找错
static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args, JValue* pResult){ StringObject* sourceNameObj = (StringObject*) args[0]; StringObject* outputNameObj = (StringObject*) args[1]; DexOrJar* pDexOrJar = NULL; JarFile* pJarFile; RawDexFile* pRawDexFile; char* sourceName; char* outputName; if (sourceNameObj == NULL) { dvmThrowNullPointerException("sourceName == null"); RETURN_VOID(); } sourceName = dvmCreateCstrFromString(sourceNameObj); if (outputNameObj != NULL) outputName = dvmCreateCstrFromString(outputNameObj); else outputName = NULL; /* * We have to deal with the possibility that somebody might try to * open one of our bootstrap class DEX files. The set of dependencies * will be different, and hence the results of optimization might be * different, which means we'd actually need to have two versions of * the optimized DEX: one that only knows about part of the boot class * path, and one that knows about everything in it. The latter might * optimize field/method accesses based on a class that appeared later * in the class path. * * We can't let the user-defined class loader open it and start using * the classes, since the optimized form of the code skips some of * the method and field resolution that we would ordinarily do, and * we'd have the wrong semantics. * * We have to reject attempts to manually open a DEX file from the boot * class path. The easiest way to do this is by filename, which works * out because variations in name (e.g. "/system/framework/./ext.jar") * result in us hitting a different dalvik-cache entry. It's also fine * if the caller specifies their own output file. */ if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) { ALOGW("Refusing to reopen boot DEX '%s'", sourceName); dvmThrowIOException( "Re-opening BOOTCLASSPATH DEX files is not allowed"); free(sourceName); free(outputName); RETURN_VOID(); } /* * Try to open it directly as a DEX if the name ends with ".dex". * If that fails (or isn't tried in the first place), try it as a * Zip with a "classes.dex" inside. */ if (hasDexExtension(sourceName) && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) { ALOGV("Opening DEX file '%s' (DEX)", sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = true; pDexOrJar->pRawDexFile = pRawDexFile; pDexOrJar->pDexMemory = NULL; } 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 { ALOGV("Unable to open DEX file '%s'", sourceName); dvmThrowIOException("unable to open DEX file"); } if (pDexOrJar != NULL) { pDexOrJar->fileName = sourceName; addToDexFileTable(pDexOrJar); } else { free(sourceName); } free(outputName); RETURN_PTR(pDexOrJar);}
dvmRawDexFileOpen函数打开一个原生dex文件,优化,加载。
函数挺长,分段看
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName, RawDexFile** ppRawDexFile, bool isBootstrap){ /* * TODO: This duplicates a lot of code from dvmJarFileOpen() in * JarFile.c. This should be refactored. */ DvmDex* pDvmDex = NULL; char* cachedName = NULL; int result = -1; int dexFd = -1; int optFd = -1; u4 modTime = 0; u4 adler32 = 0; size_t fileSize = 0; bool newFile = false; bool locked = false; dexFd = open(fileName, O_RDONLY); if (dexFd < 0) goto bail; /* If we fork/exec into dexopt, don't let it inherit the open fd. */ dvmSetCloseOnExec(dexFd);声明变量,打开一下文件,再。。。关上
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) { ALOGE("Error with header for %s", fileName); goto bail; } if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) { ALOGE("Error with stat for %s", fileName); goto bail; }之后的两个函数,第一个验证文件魔数,并且获取adler32校验码
第二个函数调用fstat函数,主要是为了获得上次修改时间,和文件大小
/* * See if the cached file matches. If so, optFd will become a reference * to the cached file and will have been seeked to just past the "opt" * header. */ if (odexOutputName == NULL) { cachedName = dexOptGenerateCacheFileName(fileName, NULL); if (cachedName == NULL) goto bail; } else { cachedName = strdup(odexOutputName); } ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)", fileName, cachedName); optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime, adler32, isBootstrap, &newFile, /*createIfMissing=*/true); if (optFd < 0) { ALOGI("Unable to open or create cache for %s (%s)", fileName, cachedName); goto bail; }在然后就是核心部分dvmOpenCachedDexFile函数,先往下看,回头再说这个函数
locked = true; /* * If optFd points to a new file (because there was no cached * version, or the cached version was stale), generate the * optimized DEX. The file descriptor returned is still locked, * and is positioned just past the optimization header. */ if (newFile) { u8 startWhen, copyWhen, endWhen; bool result; off_t dexOffset; dexOffset = lseek(optFd, 0, SEEK_CUR); result = (dexOffset > 0); if (result) { startWhen = dvmGetRelativeTimeUsec(); result = copyFileToFile(optFd, dexFd, fileSize) == 0; copyWhen = dvmGetRelativeTimeUsec(); } if (result) { result = dvmOptimizeDexFile(optFd, dexOffset, fileSize, fileName, modTime, adler32, isBootstrap); } if (!result) { ALOGE("Unable to extract+optimize DEX from '%s'", fileName); goto bail; } endWhen = dvmGetRelativeTimeUsec(); ALOGD("DEX prep '%s': copy in %dms, rewrite %dms", fileName, (int) (copyWhen - startWhen) / 1000, (int) (endWhen - copyWhen) / 1000); } /* * Map the cached version. This immediately rewinds the fd, so it * doesn't have to be seeked anywhere in particular. */ if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) { ALOGI("Unable to map cached %s", fileName); goto bail; } if (locked) { /* unlock the fd */ if (!dvmUnlockCachedDexFile(optFd)) { /* uh oh -- this process needs to exit or we'll wedge the system */ ALOGE("Unable to unlock DEX file"); goto bail; } locked = false; } ALOGV("Successfully opened '%s'", fileName); *ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile)); (*ppRawDexFile)->cacheFileName = cachedName; (*ppRawDexFile)->pDvmDex = pDvmDex; cachedName = NULL; // don't free it below result = 0;bail: free(cachedName); if (dexFd >= 0) { close(dexFd); } if (optFd >= 0) { if (locked) (void) dvmUnlockCachedDexFile(optFd); close(optFd); } return result;}比较重要的是dvmDexFileOpenFromFd函数,将dex文件拷贝到内存中
dvmOpenCachedDexFile函数位于/dalvik/vm/analysis/DexPrepare.cpp
也是很长的一个函数,分段看
int dvmOpenCachedDexFile(const char* fileName, const char* cacheFileName, u4 modWhen, u4 crc, bool isBootstrap, bool* pNewFile, bool createIfMissing){ int fd, cc; struct stat fdStat, fileStat; bool readOnly = false; *pNewFile = false;retry: /* * Try to open the cache file. If we've been asked to, * create it if it doesn't exist. */ fd = createIfMissing ? open(cacheFileName, O_CREAT|O_RDWR, 0644) : -1; if (fd < 0) { fd = open(cacheFileName, O_RDONLY, 0); if (fd < 0) { if (createIfMissing) { // TODO: write an equivalent of strerror_r that returns a std::string. const std::string errnoString(strerror(errno)); if (directoryIsValid(cacheFileName)) { ALOGE("Can't open dex cache file '%s': %s", cacheFileName, errnoString.c_str()); } } return fd; } readOnly = true; } else { fchmod(fd, 0644); }
尝试打开cacheFileName文件,否则就创建,根据调用时传递的参数可知,这个已经存在
/* * Grab an exclusive lock on the cache file. If somebody else is * working on it, we'll block here until they complete. Because * we're waiting on an external resource, we go into VMWAIT mode. */ ALOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)", cacheFileName, fd, isBootstrap); ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); cc = flock(fd, LOCK_EX | LOCK_NB); if (cc != 0) { ALOGD("DexOpt: sleeping on flock(%s)", cacheFileName); cc = flock(fd, LOCK_EX); } dvmChangeStatus(NULL, oldStatus); if (cc != 0) { ALOGE("Can't lock dex cache '%s': %d", cacheFileName, cc); close(fd); return -1; } ALOGV("DexOpt: locked cache file");这段代码看注释理解,flock函数对fd文件上锁,LOCK_EX代表互斥锁,LOCK_NB代表非阻塞。而在整个上锁期间,如果文件已被上锁,则会一直阻塞,直到flock函数
完成工作。而为了能够完成这一步,需要当前线程环境为VMWAIT,所以在flock函数前后分别用dvmChangeStatus切换当前线程状态(在dalvike启动篇也遇到这个函数,
将当前线程的状态切换)
cc = fstat(fd, &fdStat); if (cc != 0) { ALOGE("Can't stat open file '%s'", cacheFileName); LOGVV("DexOpt: unlocking cache file %s", cacheFileName); goto close_fail; } cc = stat(cacheFileName, &fileStat); if (cc != 0 || fdStat.st_dev != fileStat.st_dev || fdStat.st_ino != fileStat.st_ino)//st_dev文件所在设备id,st_ino结点编号 { ALOGD("DexOpt: our open cache file is stale; sleeping and retrying"); LOGVV("DexOpt: unlocking cache file %s", cacheFileName); flock(fd, LOCK_UN); close(fd); usleep(250 * 1000); /* if something is hosed, don't peg machine */ goto retry; }核实设备id和结点编号,但是为什么要用fstat和stat分别打开同一个文件???暂时不清楚
if (fdStat.st_size == 0) { if (readOnly) { ALOGW("DexOpt: file has zero length and isn't writable"); goto close_fail; } cc = dexOptCreateEmptyHeader(fd); if (cc != 0) goto close_fail; *pNewFile = true; ALOGV("DexOpt: successfully initialized new cache file"); } else { bool expectVerify, expectOpt; if (gDvm.classVerifyMode == VERIFY_MODE_NONE) { expectVerify = false; } else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) { expectVerify = !isBootstrap; } else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ { expectVerify = true; } if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) { expectOpt = false; } else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED || gDvm.dexOptMode == OPTIMIZE_MODE_FULL) { expectOpt = expectVerify; } else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ { expectOpt = true; } ALOGV("checking deps, expecting vfy=%d opt=%d", expectVerify, expectOpt);如果文件size为0,并且无法写入(readonly)就报错,若可以写,就产生一个空文件头。如果size不为0,进行设置
注意,PathClassLoader已经在安装阶段生成了odex,所以此处size部位0。相反,DexClassLoader就会进入size为0的分支,*pNewFile会被设置为true
而通过函数声明可以看出,这个pNewFile的值是这个函数的重要输出。
if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc, expectVerify, expectOpt)) { if (readOnly) { if (createIfMissing) { ALOGW("Cached DEX '%s' (%s) is stale and not writable", fileName, cacheFileName); } goto close_fail; } ALOGD("ODEX file is stale or bad; removing and retrying (%s)", cacheFileName); if (ftruncate(fd, 0) != 0) { ALOGW("Warning: unable to truncate cache file '%s': %s", cacheFileName, strerror(errno)); /* keep going */ } if (unlink(cacheFileName) != 0) { ALOGW("Warning: unable to remove cache file '%s': %d %s", cacheFileName, errno, strerror(errno)); /* keep going; permission failure should probably be fatal */ } LOGVV("DexOpt: unlocking cache file %s", cacheFileName); flock(fd, LOCK_UN); close(fd); goto retry; } else { ALOGV("DexOpt: good deps in cache file"); } } assert(fd >= 0); return fd;close_fail: flock(fd, LOCK_UN); close(fd); return -1;}pNewFile设置完之后就没什么了。无非是check一下文件头之类的工作。
回到主线,在看一下dvmRawDexFileOpen
if (newFile) { u8 startWhen, copyWhen, endWhen; bool result; off_t dexOffset; dexOffset = lseek(optFd, 0, SEEK_CUR); result = (dexOffset > 0); if (result) { startWhen = dvmGetRelativeTimeUsec(); result = copyFileToFile(optFd, dexFd, fileSize) == 0; copyWhen = dvmGetRelativeTimeUsec(); } if (result) { result = dvmOptimizeDexFile(optFd, dexOffset, fileSize, fileName, modTime, adler32, isBootstrap); } if (!result) { ALOGE("Unable to extract+optimize DEX from '%s'", fileName); goto bail; } endWhen = dvmGetRelativeTimeUsec(); ALOGD("DEX prep '%s': copy in %dms, rewrite %dms", fileName, (int) (copyWhen - startWhen) / 1000, (int) (endWhen - copyWhen) / 1000); }这个newFile就是刚才dvmOpenCachedDexFile输出的bool值
当newFile为true时(也就是DexClassLoader),会执行对dex的优化。获得一个空的文件头(dvmOpenCachedDexFile函数中),检查文件偏移量(lseek函数)
将原dex文件拷贝到有一个文件头的空文件当中,最后在dvmOptimizeFile函数执行优化。这个过程我专门回到上一节的优化过程比较下,与extractAndProcessZip
函数的过程大致相同,虽然函数不同,但是过程都是产生一个空文件 ——> 构造文件头 ——> 拷贝 ——> 优化。
dvmOptimizeDexFile函数同样位于/dalvik/vm/analysis/DexPrepare.cpp
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; /* * This could happen if something in our bootclasspath, which we thought * was all optimized, got rejected. */ if (gDvm.optimizing) { ALOGW("Rejecting recursive optimization attempt on '%s'", fileName); return false; } pid = fork(); if (pid == 0) { static const int kUseValgrind = 0; 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; /* * Wait for the optimization process to finish. We go into VMWAIT * mode here so GC suspension won't have to wait for us. */ 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
插播一句,在脱壳时经常使用的dvmDexFileOpenPartial函数的调用过程
从/system/bin/dexopt的main函数开始 ——> 中间函数 ——> extractAndProcessZip ——> dvmContinueOptimization ——> dvmDexFileOpenPartial
回到dvmRawDexFileOpen函数
接下来执行dvmDexFileOpenFromFd函数,位于dalvik\vm\DvmDex.cpp
int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex){ DvmDex* pDvmDex; DexFile* pDexFile; MemMapping memMap; int parseFlags = kDexParseDefault; int result = -1; if (gDvm.verifyDexChecksum) parseFlags |= kDexParseVerifyChecksum; if (lseek(fd, 0, SEEK_SET) < 0) { ALOGE("lseek rewind failed"); goto bail; } if (sysMapFileInShmemWritableReadOnly(fd, &memMap) != 0) { ALOGE("Unable to map file"); goto bail; } pDexFile = dexFileParse((u1*)memMap.addr, memMap.length, parseFlags); if (pDexFile == NULL) { ALOGE("DEX parse failed"); sysReleaseShmem(&memMap); goto bail; } pDvmDex = allocateAuxStructures(pDexFile); if (pDvmDex == NULL) { dexFileFree(pDexFile); sysReleaseShmem(&memMap); goto bail; } /* tuck this into the DexFile so it gets released later */ sysCopyMap(&pDvmDex->memMap, &memMap); pDvmDex->isMappedReadOnly = true; *ppDvmDex = pDvmDex; result = 0;bail: return result;}终于有个不长的函数了
sysMapFileInShmemWritableReadOnly函数将优化后的dex加载进内存,函数位于dalvik\libdex\SysUtil.cpp
int sysMapFileInShmemWritableReadOnly(int fd, MemMapping* pMap){#ifdef HAVE_POSIX_FILEMAP off_t start; size_t length; void* memPtr; assert(pMap != NULL); if (getFileStartAndLength(fd, &start, &length) < 0) return -1; memPtr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE, fd, start); if (memPtr == MAP_FAILED) { ALOGW("mmap(%d, R/W, FILE|PRIVATE, %d, %d) failed: %s", (int) length, fd, (int) start, strerror(errno)); return -1; } if (mprotect(memPtr, length, PROT_READ) < 0) { /* this fails with EACCESS on FAT filesystems, e.g. /sdcard */ int err = errno; ALOGV("mprotect(%p, %d, PROT_READ) failed: %s", memPtr, length, strerror(err)); ALOGD("mprotect(RO) failed (%d), file will remain read-write", err); } pMap->baseAddr = pMap->addr = memPtr; pMap->baseLength = pMap->length = length; return 0;#else return sysFakeMapFile(fd, pMap);#endif}先获得文件头位置和长度,然后用mmap函数映射到内存当中
之后执行dexFileParse,位于dalvik\libdex\DexFile.cpp
又是一个老长的函数,分段看
DexFile* dexFileParse(const u1* data, size_t length, int flags){ DexFile* pDexFile = NULL; const DexHeader* pHeader; const u1* magic; int result = -1; if (length < sizeof(DexHeader)) { ALOGE("too short to be a valid .dex"); goto bail; /* bad file format */ } pDexFile = (DexFile*) malloc(sizeof(DexFile)); if (pDexFile == NULL) goto bail; /* alloc failure */ memset(pDexFile, 0, sizeof(DexFile));检查文件大小,分配内存
if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {//验证文件魔数 magic = data; if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) { ALOGE("bad opt version (0x%02x %02x %02x %02x)", magic[4], magic[5], magic[6], magic[7]); goto bail; } pDexFile->pOptHeader = (const DexOptHeader*) data;//文件头部 ALOGV("Good opt header, DEX offset is %d, flags=0x%02x", pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags); /* parse the optimized dex file tables */ if (!dexParseOptData(data, length, pDexFile))//将优化数据与DexFile文件数据结构中相应的成员进行关联 goto bail; /* ignore the opt header and appended data from here on out */ data += pDexFile->pOptHeader->dexOffset; length -= pDexFile->pOptHeader->dexOffset; if (pDexFile->pOptHeader->dexLength > length) { ALOGE("File truncated? stored len=%d, rem len=%d", pDexFile->pOptHeader->dexLength, (int) length); goto bail; } length = pDexFile->pOptHeader->dexLength; }
dexFileSetupBasicPointers(pDexFile, data);//其他各部分数据与DexFile数据结构建立完整的映射关系 pHeader = pDexFile->pHeader; if (!dexHasValidMagic(pHeader)) { goto bail; } /* * Verify the checksum(s). This is reasonably quick, but does require * touching every byte in the DEX file. The base checksum changes after * byte-swapping and DEX optimization. */ if (flags & kDexParseVerifyChecksum) { u4 adler = dexComputeChecksum(pHeader); if (adler != pHeader->checksum) { ALOGE("ERROR: bad checksum (%08x vs %08x)", adler, pHeader->checksum); if (!(flags & kDexParseContinueOnError)) goto bail; } else { ALOGV("+++ adler32 checksum (%08x) verified", adler); } const DexOptHeader* pOptHeader = pDexFile->pOptHeader; if (pOptHeader != NULL) { adler = dexComputeOptChecksum(pOptHeader); if (adler != pOptHeader->checksum) { ALOGE("ERROR: bad opt checksum (%08x vs %08x)", adler, pOptHeader->checksum); if (!(flags & kDexParseContinueOnError)) goto bail; } else { ALOGV("+++ adler32 opt checksum (%08x) verified", adler); } } } /* * Verify the SHA-1 digest. (Normally we don't want to do this -- * the digest is used to uniquely identify the original DEX file, and * can't be computed for verification after the DEX is byte-swapped * and optimized.) */ if (kVerifySignature) { unsigned char sha1Digest[kSHA1DigestLen]; const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) + kSHA1DigestLen; dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest); if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) { char tmpBuf1[kSHA1DigestOutputLen]; char tmpBuf2[kSHA1DigestOutputLen]; ALOGE("ERROR: bad SHA1 digest (%s vs %s)", dexSHA1DigestToStr(sha1Digest, tmpBuf1), dexSHA1DigestToStr(pHeader->signature, tmpBuf2)); if (!(flags & kDexParseContinueOnError)) goto bail; } else { ALOGV("+++ sha1 digest verified"); } } if (pHeader->fileSize != length) { ALOGE("ERROR: stored file size (%d) != expected (%d)", (int) pHeader->fileSize, (int) length); if (!(flags & kDexParseContinueOnError)) goto bail; } if (pHeader->classDefsSize == 0) { ALOGE("ERROR: DEX file has no classes in it, failing"); goto bail; } /* * Success! */ result = 0;bail: if (result != 0 && pDexFile != NULL) { dexFileFree(pDexFile); pDexFile = NULL; } return pDexFile;}执行完之后回到dvmDexFileOpenFromFd函数
allocateAuxStructures函数用来生成DvmDex结构
static DvmDex* allocateAuxStructures(DexFile* pDexFile){ DvmDex* pDvmDex; const DexHeader* pHeader; u4 stringSize, classSize, methodSize, fieldSize; pHeader = pDexFile->pHeader; stringSize = pHeader->stringIdsSize * sizeof(struct StringObject*); classSize = pHeader->typeIdsSize * sizeof(struct ClassObject*); methodSize = pHeader->methodIdsSize * sizeof(struct Method*); fieldSize = pHeader->fieldIdsSize * sizeof(struct Field*); u4 totalSize = sizeof(DvmDex) + stringSize + classSize + methodSize + fieldSize; u1 *blob = (u1 *)dvmAllocRegion(totalSize, PROT_READ | PROT_WRITE, "dalvik-aux-structure"); if ((void *)blob == MAP_FAILED) return NULL; pDvmDex = (DvmDex*)blob; blob += sizeof(DvmDex); pDvmDex->pDexFile = pDexFile; pDvmDex->pHeader = pHeader; pDvmDex->pResStrings = (struct StringObject**)blob; blob += stringSize; pDvmDex->pResClasses = (struct ClassObject**)blob; blob += classSize; pDvmDex->pResMethods = (struct Method**)blob; blob += methodSize; pDvmDex->pResFields = (struct Field**)blob; ALOGV("+++ DEX %p: allocateAux (%d+%d+%d+%d)*4 = %d bytes", pDvmDex, stringSize/4, classSize/4, methodSize/4, fieldSize/4, stringSize + classSize + methodSize + fieldSize); pDvmDex->pInterfaceCache = dvmAllocAtomicCache(DEX_INTERFACE_CACHE_SIZE); dvmInitMutex(&pDvmDex->modLock); return pDvmDex;}回到dvmDexFileOpenFromFd
调用sysCopyMap将memMap关联到pDvmDex
执行完之后,回到dvmRawDexFileOpen
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile)); (*ppRawDexFile)->cacheFileName = cachedName; (*ppRawDexFile)->pDvmDex = pDvmDex; cachedName = NULL; // don't free it below result = 0;将ppRawDexFile的成员都关联起来
完成
灵魂作画,大概这样
dvmRawDexFileOpen执行完成了
回到开始的Dalvik_dalvik_system_DexFile_openDexFile
if (hasDexExtension(sourceName) && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) { ALOGV("Opening DEX file '%s' (DEX)", sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = true; pDexOrJar->pRawDexFile = pRawDexFile; pDexOrJar->pDexMemory = NULL; } 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 { ALOGV("Unable to open DEX file '%s'", sourceName); dvmThrowIOException("unable to open DEX file"); } if (pDexOrJar != NULL) { pDexOrJar->fileName = sourceName; addToDexFileTable(pDexOrJar); } else { free(sourceName); }可以看到另一个分支为dvmJarFileOpen
该函数位于dalvik\vm\JarFile.cpp
int dvmJarFileOpen(const char* fileName, const char* odexOutputName, JarFile** ppJarFile, bool isBootstrap){ /* * TODO: This function has been duplicated and modified to become * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored. */ ZipArchive archive; DvmDex* pDvmDex = NULL; char* cachedName = NULL; bool archiveOpen = false; bool locked = false; int fd = -1; int result = -1; /* Even if we're not going to look at the archive, we need to * open it so we can stuff it into ppJarFile. */ if (dexZipOpenArchive(fileName, &archive) != 0) goto bail; archiveOpen = true; /* If we fork/exec into dexopt, don't let it inherit the archive's fd. */ dvmSetCloseOnExec(dexZipGetArchiveFd(&archive)); /* First, look for a ".odex" alongside the jar file. It will * have the same name/path except for the extension. */ fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName); if (fd >= 0) { ALOGV("Using alternate file (odex) for %s ...", fileName); if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) { ALOGE("%s odex has stale dependencies", fileName); free(cachedName); cachedName = NULL; close(fd); fd = -1; goto tryArchive; } else { ALOGV("%s odex has good dependencies", fileName); //TODO: make sure that the .odex actually corresponds // to the classes.dex inside the archive (if present). // For typical use there will be no classes.dex. } } else { ZipEntry entry;tryArchive: /* * Pre-created .odex absent or stale. Look inside the jar for a * "classes.dex". */ entry = dexZipFindEntry(&archive, kDexInJarName); if (entry != NULL) { bool newFile = false; /* * We've found the one we want. See if there's an up-to-date copy * in the cache. * * On return, "fd" will be seeked just past the "opt" header. * * If a stale .odex file is present and classes.dex exists in * the archive, this will *not* return an fd pointing to the * .odex file; the fd will point into dalvik-cache like any * other jar. */ if (odexOutputName == NULL) { cachedName = dexOptGenerateCacheFileName(fileName, kDexInJarName); if (cachedName == NULL) goto bail; } else { cachedName = strdup(odexOutputName); } ALOGV("dvmJarFileOpen: Checking cache for %s (%s)", fileName, cachedName); fd = dvmOpenCachedDexFile(fileName, cachedName, dexGetZipEntryModTime(&archive, entry), dexGetZipEntryCrc32(&archive, entry), isBootstrap, &newFile, /*createIfMissing=*/true); if (fd < 0) { ALOGI("Unable to open or create cache for %s (%s)", fileName, cachedName); goto bail; } locked = true; /* * If fd points to a new file (because there was no cached version, * or the cached version was stale), generate the optimized DEX. * The file descriptor returned is still locked, and is positioned * just past the optimization header. */ if (newFile) { u8 startWhen, extractWhen, endWhen; bool result; off_t dexOffset; dexOffset = lseek(fd, 0, SEEK_CUR); result = (dexOffset > 0); if (result) { startWhen = dvmGetRelativeTimeUsec(); result = dexZipExtractEntryToFile(&archive, entry, fd) == 0; extractWhen = dvmGetRelativeTimeUsec(); } if (result) { result = dvmOptimizeDexFile(fd, dexOffset, dexGetZipEntryUncompLen(&archive, entry), fileName, dexGetZipEntryModTime(&archive, entry), dexGetZipEntryCrc32(&archive, entry), isBootstrap); } if (!result) { ALOGE("Unable to extract+optimize DEX from '%s'", fileName); goto bail; } endWhen = dvmGetRelativeTimeUsec(); ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms", fileName, (int) (extractWhen - startWhen) / 1000, (int) (endWhen - extractWhen) / 1000); } } else { ALOGI("Zip is good, but no %s inside, and no valid .odex " "file in the same directory", kDexInJarName); goto bail; } } /* * Map the cached version. This immediately rewinds the fd, so it * doesn't have to be seeked anywhere in particular. */ if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) { ALOGI("Unable to map %s in %s", kDexInJarName, fileName); goto bail; } if (locked) { /* unlock the fd */ if (!dvmUnlockCachedDexFile(fd)) { /* uh oh -- this process needs to exit or we'll wedge the system */ ALOGE("Unable to unlock DEX file"); goto bail; } locked = false; } ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName); *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile)); (*ppJarFile)->archive = archive; (*ppJarFile)->cacheFileName = cachedName; (*ppJarFile)->pDvmDex = pDvmDex; cachedName = NULL; // don't free it below result = 0;bail: /* clean up, closing the open file */ if (archiveOpen && result != 0) dexZipCloseArchive(&archive); free(cachedName); if (fd >= 0) { if (locked) (void) dvmUnlockCachedDexFile(fd); close(fd); } return result;}跟dvmRawDexFileOpen一样,也很多
不过过程大同小异,无非是函数不一样,每一步的目的都是相同的
dvmOpenCachedDexFile、dvmOptimizeDexFile、dvmDexFileOpenFromFd这些关键函数没有变。
简单看一下,就不做详细分析了。
这篇,从上周写到这周,内容超多,还是要时常会看,复习。总结下来,关键步骤也就那几步。
- Android dex加载过程分析
- Android动态加载DEX文件流程分析
- Android编译及Dex过程源码分析
- dex: 类加载过程
- android dex加载跟踪
- android compile tasks中dex过程源码情景分析
- android compile tasks中dex过程源码情景分析
- 【分析】多dex加载机制
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- Android动态加载jar/dex
- javascript入门
- 8.5总结
- win10 Scrapy遇到的问题
- 文件夹之间的拷贝
- hdu 4715 Difference Between Primes 筛法
- Android dex加载过程分析
- java 多线程的三种创建方式
- Module won't show up in “Edit Configuration”
- UVA 11624--Fire!
- 中软Java学习第八天笔记之Set
- c++ 学习之路 第六天 auto decltype
- java异常处理机制
- 串结构练习——字符串连接
- gcc 源码官网下载