| /* |
| * 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. |
| */ |
| |
| /* |
| * Prepare a DEX file for use by the VM. Depending upon the VM options |
| * we will attempt to verify and/or optimize the code, possibly appending |
| * register maps. |
| * |
| * TODO: the format of the optimized header is currently "whatever we |
| * happen to write", since the VM that writes it is by definition the same |
| * as the VM that reads it. Still, it should be better documented and |
| * more rigorously structured. |
| */ |
| #include "Dalvik.h" |
| #include "libdex/OptInvocation.h" |
| #include "analysis/RegisterMap.h" |
| #include "analysis/Optimize.h" |
| |
| #include <string> |
| |
| #include <libgen.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <zlib.h> |
| |
| /* fwd */ |
| static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt, |
| DexClassLookup** ppClassLookup, DvmDex** ppDvmDex); |
| static bool loadAllClasses(DvmDex* pDvmDex); |
| static void verifyAndOptimizeClasses(DexFile* pDexFile, bool doVerify, |
| bool doOpt); |
| static void verifyAndOptimizeClass(DexFile* pDexFile, ClassObject* clazz, |
| const DexClassDef* pClassDef, bool doVerify, bool doOpt); |
| static void updateChecksum(u1* addr, int len, DexHeader* pHeader); |
| static int writeDependencies(int fd, u4 modWhen, u4 crc); |
| static bool writeOptData(int fd, const DexClassLookup* pClassLookup,\ |
| const RegisterMapBuilder* pRegMapBuilder); |
| static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum); |
| |
| /* |
| * Get just the directory portion of the given path. Equivalent to dirname(3). |
| */ |
| static std::string saneDirName(const std::string& path) { |
| size_t n = path.rfind('/'); |
| if (n == std::string::npos) { |
| return "."; |
| } |
| return path.substr(0, n); |
| } |
| |
| /* |
| * Helper for dvmOpenCacheDexFile() in a known-error case: Check to |
| * see if the directory part of the given path (all but the last |
| * component) exists and is writable. Complain to the log if not. |
| */ |
| static bool directoryIsValid(const std::string& fileName) |
| { |
| std::string dirName(saneDirName(fileName)); |
| |
| struct stat sb; |
| if (stat(dirName.c_str(), &sb) < 0) { |
| LOGE("Could not stat dex cache directory '%s': %s", dirName.c_str(), strerror(errno)); |
| return false; |
| } |
| |
| if (!S_ISDIR(sb.st_mode)) { |
| LOGE("Dex cache directory isn't a directory: %s", dirName.c_str()); |
| return false; |
| } |
| |
| if (access(dirName.c_str(), W_OK) < 0) { |
| LOGE("Dex cache directory isn't writable: %s", dirName.c_str()); |
| return false; |
| } |
| |
| if (access(dirName.c_str(), R_OK) < 0) { |
| LOGE("Dex cache directory isn't readable: %s", dirName.c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Return the fd of an open file in the DEX file cache area. If the cache |
| * file doesn't exist or is out of date, this will remove the old entry, |
| * create a new one (writing only the file header), and return with the |
| * "new file" flag set. |
| * |
| * It's possible to execute from an unoptimized DEX file directly, |
| * assuming the byte ordering and structure alignment is correct, but |
| * disadvantageous because some significant optimizations are not possible. |
| * It's not generally possible to do the same from an uncompressed Jar |
| * file entry, because we have to guarantee 32-bit alignment in the |
| * memory-mapped file. |
| * |
| * For a Jar/APK file (a zip archive with "classes.dex" inside), "modWhen" |
| * and "crc32" come from the Zip directory entry. For a stand-alone DEX |
| * file, it's the modification date of the file and the Adler32 from the |
| * DEX header (which immediately follows the magic). If these don't |
| * match what's stored in the opt header, we reject the file immediately. |
| * |
| * On success, the file descriptor will be positioned just past the "opt" |
| * file header, and will be locked with flock. "*pCachedName" will point |
| * to newly-allocated storage. |
| */ |
| 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)) { |
| LOGE("Can't open dex cache file '%s': %s", cacheFileName, errnoString.c_str()); |
| } |
| } |
| return fd; |
| } |
| readOnly = true; |
| } else { |
| fchmod(fd, 0644); |
| } |
| |
| /* |
| * 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. |
| */ |
| LOGV("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) { |
| LOGD("DexOpt: sleeping on flock(%s)", cacheFileName); |
| cc = flock(fd, LOCK_EX); |
| } |
| dvmChangeStatus(NULL, oldStatus); |
| if (cc != 0) { |
| LOGE("Can't lock dex cache '%s': %d", cacheFileName, cc); |
| close(fd); |
| return -1; |
| } |
| LOGV("DexOpt: locked cache file"); |
| |
| /* |
| * Check to see if the fd we opened and locked matches the file in |
| * the filesystem. If they don't, then somebody else unlinked ours |
| * and created a new file, and we need to use that one instead. (If |
| * we caught them between the unlink and the create, we'll get an |
| * ENOENT from the file stat.) |
| */ |
| cc = fstat(fd, &fdStat); |
| if (cc != 0) { |
| LOGE("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) |
| { |
| LOGD("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; |
| } |
| |
| /* |
| * We have the correct file open and locked. If the file size is zero, |
| * then it was just created by us, and we want to fill in some fields |
| * in the "opt" header and set "*pNewFile". Otherwise, we want to |
| * verify that the fields in the header match our expectations, and |
| * reset the file if they don't. |
| */ |
| if (fdStat.st_size == 0) { |
| if (readOnly) { |
| LOGW("DexOpt: file has zero length and isn't writable"); |
| goto close_fail; |
| } |
| cc = dexOptCreateEmptyHeader(fd); |
| if (cc != 0) |
| goto close_fail; |
| *pNewFile = true; |
| LOGV("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; |
| } |
| |
| LOGV("checking deps, expecting vfy=%d opt=%d", |
| expectVerify, expectOpt); |
| |
| if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc, |
| expectVerify, expectOpt)) |
| { |
| if (readOnly) { |
| /* |
| * We could unlink and rewrite the file if we own it or |
| * the "sticky" bit isn't set on the directory. However, |
| * we're not able to truncate it, which spoils things. So, |
| * give up now. |
| */ |
| if (createIfMissing) { |
| LOGW("Cached DEX '%s' (%s) is stale and not writable", |
| fileName, cacheFileName); |
| } |
| goto close_fail; |
| } |
| |
| /* |
| * If we truncate the existing file before unlinking it, any |
| * process that has it mapped will fail when it tries to touch |
| * the pages. |
| * |
| * This is very important. The zygote process will have the |
| * boot DEX files (core, framework, etc.) mapped early. If |
| * (say) core.dex gets updated, and somebody launches an app |
| * that uses App.dex, then App.dex gets reoptimized because it's |
| * dependent upon the boot classes. However, dexopt will be |
| * using the *new* core.dex to do the optimizations, while the |
| * app will actually be running against the *old* core.dex |
| * because it starts from zygote. |
| * |
| * Even without zygote, it's still possible for a class loader |
| * to pull in an APK that was optimized against an older set |
| * of DEX files. We must ensure that everything fails when a |
| * boot DEX gets updated, and for general "why aren't my |
| * changes doing anything" purposes its best if we just make |
| * everything crash when a DEX they're using gets updated. |
| */ |
| LOGD("ODEX file is stale or bad; removing and retrying (%s)", |
| cacheFileName); |
| if (ftruncate(fd, 0) != 0) { |
| LOGW("Warning: unable to truncate cache file '%s': %s", |
| cacheFileName, strerror(errno)); |
| /* keep going */ |
| } |
| if (unlink(cacheFileName) != 0) { |
| LOGW("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 { |
| LOGV("DexOpt: good deps in cache file"); |
| } |
| } |
| |
| assert(fd >= 0); |
| return fd; |
| |
| close_fail: |
| flock(fd, LOCK_UN); |
| close(fd); |
| return -1; |
| } |
| |
| /* |
| * Unlock the file descriptor. |
| * |
| * Returns "true" on success. |
| */ |
| bool dvmUnlockCachedDexFile(int fd) |
| { |
| LOGVV("DexOpt: unlocking cache file fd=%d", fd); |
| return (flock(fd, LOCK_UN) == 0); |
| } |
| |
| |
| /* |
| * Given a descriptor for a file with DEX data in it, produce an |
| * optimized version. |
| * |
| * The file pointed to by "fd" is expected to be a locked shared resource |
| * (or private); we make no efforts to enforce multi-process correctness |
| * here. |
| * |
| * "fileName" is only used for debug output. "modWhen" and "crc" are stored |
| * in the dependency set. |
| * |
| * The "isBootstrap" flag determines how the optimizer and verifier handle |
| * package-scope access checks. When optimizing, we only load the bootstrap |
| * class DEX files and the target DEX, so the flag determines whether the |
| * target DEX classes are given a (synthetic) non-NULL classLoader pointer. |
| * This only really matters if the target DEX contains classes that claim to |
| * be in the same package as bootstrap classes. |
| * |
| * The optimizer will need to load every class in the target DEX file. |
| * This is generally undesirable, so we start a subprocess to do the |
| * work and wait for it to complete. |
| * |
| * Returns "true" on success. All data will have been written to "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; |
| |
| LOGD("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) { |
| LOGW("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) { |
| LOGW("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)); |
| |
| LOGE("execv '%s'%s failed: %s", execFile, |
| kUseValgrind ? " [valgrind]" : "", strerror(errno)); |
| exit(1); |
| } else { |
| LOGV("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) { |
| LOGD("waitpid interrupted, retrying"); |
| } else { |
| break; |
| } |
| } |
| dvmChangeStatus(NULL, oldStatus); |
| if (gotPid != pid) { |
| LOGE("waitpid failed: wanted %d, got %d: %s", |
| (int) pid, (int) gotPid, strerror(errno)); |
| return false; |
| } |
| |
| if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { |
| LOGD("DexOpt: --- END '%s' (success) ---", lastPart); |
| return true; |
| } else { |
| LOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed", |
| lastPart, status); |
| return false; |
| } |
| } |
| } |
| |
| /* |
| * Do the actual optimization. This is executed in the dexopt process. |
| * |
| * For best use of disk/memory, we want to extract once and perform |
| * optimizations in place. If the file has to expand or contract |
| * to match local structure padding/alignment expectations, we want |
| * to do the rewrite as part of the extract, rather than extracting |
| * into a temp file and slurping it back out. (The structure alignment |
| * is currently correct for all platforms, and this isn't expected to |
| * change, so we should be okay with having it already extracted.) |
| * |
| * Returns "true" on success. |
| */ |
| bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength, |
| const char* fileName, u4 modWhen, u4 crc, bool isBootstrap) |
| { |
| DexClassLookup* pClassLookup = NULL; |
| RegisterMapBuilder* pRegMapBuilder = NULL; |
| |
| assert(gDvm.optimizing); |
| |
| LOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap); |
| |
| assert(dexOffset >= 0); |
| |
| /* quick test so we don't blow up on empty file */ |
| if (dexLength < (int) sizeof(DexHeader)) { |
| LOGE("too small to be DEX"); |
| return false; |
| } |
| if (dexOffset < (int) sizeof(DexOptHeader)) { |
| LOGE("not enough room for opt header"); |
| return false; |
| } |
| |
| bool result = false; |
| |
| /* |
| * Drop this into a global so we don't have to pass it around. We could |
| * also add a field to DexFile, but since it only pertains to DEX |
| * creation that probably doesn't make sense. |
| */ |
| gDvm.optimizingBootstrapClass = isBootstrap; |
| |
| { |
| /* |
| * Map the entire file (so we don't have to worry about page |
| * alignment). The expectation is that the output file contains |
| * our DEX data plus room for a small header. |
| */ |
| bool success; |
| void* mapAddr; |
| mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE, |
| MAP_SHARED, fd, 0); |
| if (mapAddr == MAP_FAILED) { |
| LOGE("unable to mmap DEX cache: %s", strerror(errno)); |
| goto bail; |
| } |
| |
| bool doVerify, doOpt; |
| if (gDvm.classVerifyMode == VERIFY_MODE_NONE) { |
| doVerify = false; |
| } else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) { |
| doVerify = !gDvm.optimizingBootstrapClass; |
| } else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ { |
| doVerify = true; |
| } |
| |
| if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) { |
| doOpt = false; |
| } else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED || |
| gDvm.dexOptMode == OPTIMIZE_MODE_FULL) { |
| doOpt = doVerify; |
| } else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ { |
| doOpt = true; |
| } |
| |
| /* |
| * Rewrite the file. Byte reordering, structure realigning, |
| * class verification, and bytecode optimization are all performed |
| * here. |
| * |
| * In theory the file could change size and bits could shift around. |
| * In practice this would be annoying to deal with, so the file |
| * layout is designed so that it can always be rewritten in place. |
| * |
| * This creates the class lookup table as part of doing the processing. |
| */ |
| success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, |
| doVerify, doOpt, &pClassLookup, NULL); |
| |
| if (success) { |
| DvmDex* pDvmDex = NULL; |
| u1* dexAddr = ((u1*) mapAddr) + dexOffset; |
| |
| if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) { |
| LOGE("Unable to create DexFile"); |
| success = false; |
| } else { |
| /* |
| * If configured to do so, generate register map output |
| * for all verified classes. The register maps were |
| * generated during verification, and will now be serialized. |
| */ |
| if (gDvm.generateRegisterMaps) { |
| pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex); |
| if (pRegMapBuilder == NULL) { |
| LOGE("Failed generating register maps"); |
| success = false; |
| } |
| } |
| |
| DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader; |
| updateChecksum(dexAddr, dexLength, pHeader); |
| |
| dvmDexFileFree(pDvmDex); |
| } |
| } |
| |
| /* unmap the read-write version, forcing writes to disk */ |
| if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) { |
| LOGW("msync failed: %s", strerror(errno)); |
| // weird, but keep going |
| } |
| #if 1 |
| /* |
| * This causes clean shutdown to fail, because we have loaded classes |
| * that point into it. For the optimizer this isn't a problem, |
| * because it's more efficient for the process to simply exit. |
| * Exclude this code when doing clean shutdown for valgrind. |
| */ |
| if (munmap(mapAddr, dexOffset + dexLength) != 0) { |
| LOGE("munmap failed: %s", strerror(errno)); |
| goto bail; |
| } |
| #endif |
| |
| if (!success) |
| goto bail; |
| } |
| |
| /* get start offset, and adjust deps start for 64-bit alignment */ |
| off_t depsOffset, optOffset, endOffset, adjOffset; |
| int depsLength, optLength; |
| u4 optChecksum; |
| |
| depsOffset = lseek(fd, 0, SEEK_END); |
| if (depsOffset < 0) { |
| LOGE("lseek to EOF failed: %s", strerror(errno)); |
| goto bail; |
| } |
| adjOffset = (depsOffset + 7) & ~(0x07); |
| if (adjOffset != depsOffset) { |
| LOGV("Adjusting deps start from %d to %d", |
| (int) depsOffset, (int) adjOffset); |
| depsOffset = adjOffset; |
| lseek(fd, depsOffset, SEEK_SET); |
| } |
| |
| /* |
| * Append the dependency list. |
| */ |
| if (writeDependencies(fd, modWhen, crc) != 0) { |
| LOGW("Failed writing dependencies"); |
| goto bail; |
| } |
| |
| /* compute deps length, then adjust opt start for 64-bit alignment */ |
| optOffset = lseek(fd, 0, SEEK_END); |
| depsLength = optOffset - depsOffset; |
| |
| adjOffset = (optOffset + 7) & ~(0x07); |
| if (adjOffset != optOffset) { |
| LOGV("Adjusting opt start from %d to %d", |
| (int) optOffset, (int) adjOffset); |
| optOffset = adjOffset; |
| lseek(fd, optOffset, SEEK_SET); |
| } |
| |
| /* |
| * Append any optimized pre-computed data structures. |
| */ |
| if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) { |
| LOGW("Failed writing opt data"); |
| goto bail; |
| } |
| |
| endOffset = lseek(fd, 0, SEEK_END); |
| optLength = endOffset - optOffset; |
| |
| /* compute checksum from start of deps to end of opt area */ |
| if (!computeFileChecksum(fd, depsOffset, |
| (optOffset+optLength) - depsOffset, &optChecksum)) |
| { |
| goto bail; |
| } |
| |
| /* |
| * Output the "opt" header with all values filled in and a correct |
| * magic number. |
| */ |
| DexOptHeader optHdr; |
| memset(&optHdr, 0xff, sizeof(optHdr)); |
| memcpy(optHdr.magic, DEX_OPT_MAGIC, 4); |
| memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4); |
| optHdr.dexOffset = (u4) dexOffset; |
| optHdr.dexLength = (u4) dexLength; |
| optHdr.depsOffset = (u4) depsOffset; |
| optHdr.depsLength = (u4) depsLength; |
| optHdr.optOffset = (u4) optOffset; |
| optHdr.optLength = (u4) optLength; |
| #if __BYTE_ORDER != __LITTLE_ENDIAN |
| optHdr.flags = DEX_OPT_FLAG_BIG; |
| #else |
| optHdr.flags = 0; |
| #endif |
| optHdr.checksum = optChecksum; |
| |
| fsync(fd); /* ensure previous writes go before header is written */ |
| |
| lseek(fd, 0, SEEK_SET); |
| if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0) |
| goto bail; |
| |
| LOGV("Successfully wrote DEX header"); |
| result = true; |
| |
| //dvmRegisterMapDumpStats(); |
| |
| bail: |
| dvmFreeRegisterMapBuilder(pRegMapBuilder); |
| free(pClassLookup); |
| return result; |
| } |
| |
| /* |
| * Prepare an in-memory DEX file. |
| * |
| * The data was presented to the VM as a byte array rather than a file. |
| * We want to do the same basic set of operations, but we can just leave |
| * them in memory instead of writing them out to a cached optimized DEX file. |
| */ |
| bool dvmPrepareDexInMemory(u1* addr, size_t len, DvmDex** ppDvmDex) |
| { |
| DexClassLookup* pClassLookup = NULL; |
| |
| /* |
| * Byte-swap, realign, verify basic DEX file structure. |
| * |
| * We could load + verify + optimize here as well, but that's probably |
| * not desirable. |
| * |
| * (The bulk-verification code is currently only setting the DEX |
| * file's "verified" flag, not updating the ClassObject. This would |
| * also need to be changed, or we will try to verify the class twice, |
| * and possibly reject it when optimized opcodes are encountered.) |
| */ |
| if (!rewriteDex(addr, len, false, false, &pClassLookup, ppDvmDex)) { |
| return false; |
| } |
| |
| (*ppDvmDex)->pDexFile->pClassLookup = pClassLookup; |
| |
| return true; |
| } |
| |
| /* |
| * Perform in-place rewrites on a memory-mapped DEX file. |
| * |
| * If this is called from a short-lived child process (dexopt), we can |
| * go nutty with loading classes and allocating memory. When it's |
| * called to prepare classes provided in a byte array, we may want to |
| * be more conservative. |
| * |
| * If "ppClassLookup" is non-NULL, a pointer to a newly-allocated |
| * DexClassLookup will be returned on success. |
| * |
| * If "ppDvmDex" is non-NULL, a newly-allocated DvmDex struct will be |
| * returned on success. |
| */ |
| static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt, |
| DexClassLookup** ppClassLookup, DvmDex** ppDvmDex) |
| { |
| DexClassLookup* pClassLookup = NULL; |
| u8 prepWhen, loadWhen, verifyOptWhen; |
| DvmDex* pDvmDex = NULL; |
| bool result = false; |
| const char* msgStr = "???"; |
| |
| /* if the DEX is in the wrong byte order, swap it now */ |
| if (dexSwapAndVerify(addr, len) != 0) |
| goto bail; |
| |
| /* |
| * Now that the DEX file can be read directly, create a DexFile struct |
| * for it. |
| */ |
| if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) { |
| LOGE("Unable to create DexFile"); |
| goto bail; |
| } |
| |
| /* |
| * Create the class lookup table. This will eventually be appended |
| * to the end of the .odex. |
| * |
| * We create a temporary link from the DexFile for the benefit of |
| * class loading, below. |
| */ |
| pClassLookup = dexCreateClassLookup(pDvmDex->pDexFile); |
| if (pClassLookup == NULL) |
| goto bail; |
| pDvmDex->pDexFile->pClassLookup = pClassLookup; |
| |
| /* |
| * If we're not going to attempt to verify or optimize the classes, |
| * there's no value in loading them, so bail out early. |
| */ |
| if (!doVerify && !doOpt) { |
| result = true; |
| goto bail; |
| } |
| |
| prepWhen = dvmGetRelativeTimeUsec(); |
| |
| /* |
| * Load all classes found in this DEX file. If they fail to load for |
| * some reason, they won't get verified (which is as it should be). |
| */ |
| if (!loadAllClasses(pDvmDex)) |
| goto bail; |
| loadWhen = dvmGetRelativeTimeUsec(); |
| |
| /* |
| * Create a data structure for use by the bytecode optimizer. |
| * We need to look up methods in a few classes, so this may cause |
| * a bit of class loading. We usually do this during VM init, but |
| * for dexopt on core.jar the order of operations gets a bit tricky, |
| * so we defer it to here. |
| */ |
| if (!dvmCreateInlineSubsTable()) |
| goto bail; |
| |
| /* |
| * Verify and optimize all classes in the DEX file (command-line |
| * options permitting). |
| * |
| * This is best-effort, so there's really no way for dexopt to |
| * fail at this point. |
| */ |
| verifyAndOptimizeClasses(pDvmDex->pDexFile, doVerify, doOpt); |
| verifyOptWhen = dvmGetRelativeTimeUsec(); |
| |
| if (doVerify && doOpt) |
| msgStr = "verify+opt"; |
| else if (doVerify) |
| msgStr = "verify"; |
| else if (doOpt) |
| msgStr = "opt"; |
| LOGD("DexOpt: load %dms, %s %dms", |
| (int) (loadWhen - prepWhen) / 1000, |
| msgStr, |
| (int) (verifyOptWhen - loadWhen) / 1000); |
| |
| result = true; |
| |
| bail: |
| /* |
| * On success, return the pieces that the caller asked for. |
| */ |
| |
| if (pDvmDex != NULL) { |
| /* break link between the two */ |
| pDvmDex->pDexFile->pClassLookup = NULL; |
| } |
| |
| if (ppDvmDex == NULL || !result) { |
| dvmDexFileFree(pDvmDex); |
| } else { |
| *ppDvmDex = pDvmDex; |
| } |
| |
| if (ppClassLookup == NULL || !result) { |
| free(pClassLookup); |
| } else { |
| *ppClassLookup = pClassLookup; |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Try to load all classes in the specified DEX. If they have some sort |
| * of broken dependency, e.g. their superclass lives in a different DEX |
| * that wasn't previously loaded into the bootstrap class path, loading |
| * will fail. This is the desired behavior. |
| * |
| * We have no notion of class loader at this point, so we load all of |
| * the classes with the bootstrap class loader. It turns out this has |
| * exactly the behavior we want, and has no ill side effects because we're |
| * running in a separate process and anything we load here will be forgotten. |
| * |
| * We set the CLASS_MULTIPLE_DEFS flag here if we see multiple definitions. |
| * This works because we only call here as part of optimization / pre-verify, |
| * not during verification as part of loading a class into a running VM. |
| * |
| * This returns "false" if the world is too screwed up to do anything |
| * useful at all. |
| */ |
| static bool loadAllClasses(DvmDex* pDvmDex) |
| { |
| u4 count = pDvmDex->pDexFile->pHeader->classDefsSize; |
| u4 idx; |
| int loaded = 0; |
| |
| LOGV("DexOpt: +++ trying to load %d classes", count); |
| |
| dvmSetBootPathExtraDex(pDvmDex); |
| |
| /* |
| * At this point, it is safe -- and necessary! -- to look up the |
| * VM's required classes and members, even when what we are in the |
| * process of processing is the core library that defines these |
| * classes itself. (The reason it is necessary is that in the act |
| * of initializing the class Class, below, the system will end up |
| * referring to many of the class references that got set up by |
| * this call.) |
| */ |
| if (!dvmFindRequiredClassesAndMembers()) { |
| return false; |
| } |
| |
| /* |
| * We have some circularity issues with Class and Object that are |
| * most easily avoided by ensuring that Object is never the first |
| * thing we try to find-and-initialize. The call to |
| * dvmFindSystemClass() here takes care of that situation. (We |
| * only need to do this when loading classes from the DEX file |
| * that contains Object, and only when Object comes first in the |
| * list, but it costs very little to do it in all cases.) |
| */ |
| if (!dvmInitClass(gDvm.classJavaLangClass)) { |
| LOGE("ERROR: failed to initialize the class Class!"); |
| return false; |
| } |
| |
| for (idx = 0; idx < count; idx++) { |
| const DexClassDef* pClassDef; |
| const char* classDescriptor; |
| ClassObject* newClass; |
| |
| pClassDef = dexGetClassDef(pDvmDex->pDexFile, idx); |
| classDescriptor = |
| dexStringByTypeIdx(pDvmDex->pDexFile, pClassDef->classIdx); |
| |
| LOGV("+++ loading '%s'", classDescriptor); |
| //newClass = dvmDefineClass(pDexFile, classDescriptor, |
| // NULL); |
| newClass = dvmFindSystemClassNoInit(classDescriptor); |
| if (newClass == NULL) { |
| LOGV("DexOpt: failed loading '%s'", classDescriptor); |
| dvmClearOptException(dvmThreadSelf()); |
| } else if (newClass->pDvmDex != pDvmDex) { |
| /* |
| * We don't load the new one, and we tag the first one found |
| * with the "multiple def" flag so the resolver doesn't try |
| * to make it available. |
| */ |
| LOGD("DexOpt: '%s' has an earlier definition; blocking out", |
| classDescriptor); |
| SET_CLASS_FLAG(newClass, CLASS_MULTIPLE_DEFS); |
| } else { |
| loaded++; |
| } |
| } |
| LOGV("DexOpt: +++ successfully loaded %d classes", loaded); |
| |
| dvmSetBootPathExtraDex(NULL); |
| return true; |
| } |
| |
| /* |
| * Verify and/or optimize all classes that were successfully loaded from |
| * this DEX file. |
| */ |
| static void verifyAndOptimizeClasses(DexFile* pDexFile, bool doVerify, |
| bool doOpt) |
| { |
| u4 count = pDexFile->pHeader->classDefsSize; |
| u4 idx; |
| |
| for (idx = 0; idx < count; idx++) { |
| const DexClassDef* pClassDef; |
| const char* classDescriptor; |
| ClassObject* clazz; |
| |
| pClassDef = dexGetClassDef(pDexFile, idx); |
| classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx); |
| |
| /* all classes are loaded into the bootstrap class loader */ |
| clazz = dvmLookupClass(classDescriptor, NULL, false); |
| if (clazz != NULL) { |
| verifyAndOptimizeClass(pDexFile, clazz, pClassDef, doVerify, doOpt); |
| |
| } else { |
| // TODO: log when in verbose mode |
| LOGV("DexOpt: not optimizing unavailable class '%s'", |
| classDescriptor); |
| } |
| } |
| |
| #ifdef VERIFIER_STATS |
| LOGI("Verifier stats:"); |
| LOGI(" methods examined : %u", gDvm.verifierStats.methodsExamined); |
| LOGI(" monitor-enter methods : %u", gDvm.verifierStats.monEnterMethods); |
| LOGI(" instructions examined : %u", gDvm.verifierStats.instrsExamined); |
| LOGI(" instructions re-examined: %u", gDvm.verifierStats.instrsReexamined); |
| LOGI(" copying of register sets: %u", gDvm.verifierStats.copyRegCount); |
| LOGI(" merging of register sets: %u", gDvm.verifierStats.mergeRegCount); |
| LOGI(" ...that caused changes : %u", gDvm.verifierStats.mergeRegChanged); |
| LOGI(" uninit searches : %u", gDvm.verifierStats.uninitSearches); |
| LOGI(" max memory required : %u", gDvm.verifierStats.biggestAlloc); |
| #endif |
| } |
| |
| /* |
| * Verify and/or optimize a specific class. |
| */ |
| static void verifyAndOptimizeClass(DexFile* pDexFile, ClassObject* clazz, |
| const DexClassDef* pClassDef, bool doVerify, bool doOpt) |
| { |
| const char* classDescriptor; |
| bool verified = false; |
| |
| if (clazz->pDvmDex->pDexFile != pDexFile) { |
| /* |
| * The current DEX file defined a class that is also present in the |
| * bootstrap class path. The class loader favored the bootstrap |
| * version, which means that we have a pointer to a class that is |
| * (a) not the one we want to examine, and (b) mapped read-only, |
| * so we will seg fault if we try to rewrite instructions inside it. |
| */ |
| LOGD("DexOpt: not verifying/optimizing '%s': multiple definitions", |
| clazz->descriptor); |
| return; |
| } |
| |
| classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx); |
| |
| /* |
| * First, try to verify it. |
| */ |
| if (doVerify) { |
| if (dvmVerifyClass(clazz)) { |
| /* |
| * Set the "is preverified" flag in the DexClassDef. We |
| * do it here, rather than in the ClassObject structure, |
| * because the DexClassDef is part of the odex file. |
| */ |
| assert((clazz->accessFlags & JAVA_FLAGS_MASK) == |
| pClassDef->accessFlags); |
| ((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISPREVERIFIED; |
| verified = true; |
| } else { |
| // TODO: log when in verbose mode |
| LOGV("DexOpt: '%s' failed verification", classDescriptor); |
| } |
| } |
| |
| if (doOpt) { |
| bool needVerify = (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED || |
| gDvm.dexOptMode == OPTIMIZE_MODE_FULL); |
| if (!verified && needVerify) { |
| LOGV("DexOpt: not optimizing '%s': not verified", |
| classDescriptor); |
| } else { |
| dvmOptimizeClass(clazz, false); |
| |
| /* set the flag whether or not we actually changed anything */ |
| ((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISOPTIMIZED; |
| } |
| } |
| } |
| |
| |
| /* |
| * Get the cache file name from a ClassPathEntry. |
| */ |
| static const char* getCacheFileName(const ClassPathEntry* cpe) |
| { |
| switch (cpe->kind) { |
| case kCpeJar: |
| return dvmGetJarFileCacheFileName((JarFile*) cpe->ptr); |
| case kCpeDex: |
| return dvmGetRawDexFileCacheFileName((RawDexFile*) cpe->ptr); |
| default: |
| LOGE("DexOpt: unexpected cpe kind %d", cpe->kind); |
| dvmAbort(); |
| return NULL; |
| } |
| } |
| |
| /* |
| * Get the SHA-1 signature. |
| */ |
| static const u1* getSignature(const ClassPathEntry* cpe) |
| { |
| DvmDex* pDvmDex; |
| |
| switch (cpe->kind) { |
| case kCpeJar: |
| pDvmDex = dvmGetJarFileDex((JarFile*) cpe->ptr); |
| break; |
| case kCpeDex: |
| pDvmDex = dvmGetRawDexFileDex((RawDexFile*) cpe->ptr); |
| break; |
| default: |
| LOGE("unexpected cpe kind %d", cpe->kind); |
| dvmAbort(); |
| pDvmDex = NULL; // make gcc happy |
| } |
| |
| assert(pDvmDex != NULL); |
| return pDvmDex->pDexFile->pHeader->signature; |
| } |
| |
| |
| /* |
| * Dependency layout: |
| * 4b Source file modification time, in seconds since 1970 UTC |
| * 4b CRC-32 from Zip entry, or Adler32 from source DEX header |
| * 4b Dalvik VM build number |
| * 4b Number of dependency entries that follow |
| * Dependency entries: |
| * 4b Name length (including terminating null) |
| * var Full path of cache entry (null terminated) |
| * 20b SHA-1 signature from source DEX file |
| * |
| * If this changes, update DEX_OPT_MAGIC_VERS. |
| */ |
| static const size_t kMinDepSize = 4 * 4; |
| static const size_t kMaxDepSize = 4 * 4 + 2048; // sanity check |
| |
| /* |
| * Read the "opt" header, verify it, then read the dependencies section |
| * and verify that data as well. |
| * |
| * If "sourceAvail" is "true", this will verify that "modWhen" and "crc" |
| * match up with what is stored in the header. If they don't, we reject |
| * the file so that it can be recreated from the updated original. If |
| * "sourceAvail" isn't set, e.g. for a .odex file, we ignore these arguments. |
| * |
| * On successful return, the file will be seeked immediately past the |
| * "opt" header. |
| */ |
| bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen, |
| u4 crc, bool expectVerify, bool expectOpt) |
| { |
| DexOptHeader optHdr; |
| u1* depData = NULL; |
| const u1* magic; |
| off_t posn; |
| int result = false; |
| ssize_t actual; |
| |
| /* |
| * Start at the start. The "opt" header, when present, will always be |
| * the first thing in the file. |
| */ |
| if (lseek(fd, 0, SEEK_SET) != 0) { |
| LOGE("DexOpt: failed to seek to start of file: %s", strerror(errno)); |
| goto bail; |
| } |
| |
| /* |
| * Read and do trivial verification on the opt header. The header is |
| * always in host byte order. |
| */ |
| actual = read(fd, &optHdr, sizeof(optHdr)); |
| if (actual < 0) { |
| LOGE("DexOpt: failed reading opt header: %s", strerror(errno)); |
| goto bail; |
| } else if (actual != sizeof(optHdr)) { |
| LOGE("DexOpt: failed reading opt header (got %d of %zd)", |
| (int) actual, sizeof(optHdr)); |
| goto bail; |
| } |
| |
| magic = optHdr.magic; |
| if (memcmp(magic, DEX_MAGIC, 4) == 0) { |
| /* somebody probably pointed us at the wrong file */ |
| LOGD("DexOpt: expected optimized DEX, found unoptimized"); |
| goto bail; |
| } else if (memcmp(magic, DEX_OPT_MAGIC, 4) != 0) { |
| /* not a DEX file, or previous attempt was interrupted */ |
| LOGD("DexOpt: incorrect opt magic number (0x%02x %02x %02x %02x)", |
| magic[0], magic[1], magic[2], magic[3]); |
| goto bail; |
| } |
| if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) { |
| LOGW("DexOpt: stale opt version (0x%02x %02x %02x %02x)", |
| magic[4], magic[5], magic[6], magic[7]); |
| goto bail; |
| } |
| if (optHdr.depsLength < kMinDepSize || optHdr.depsLength > kMaxDepSize) { |
| LOGW("DexOpt: weird deps length %d, bailing", optHdr.depsLength); |
| goto bail; |
| } |
| |
| /* |
| * Do the header flags match up with what we want? |
| * |
| * The only thing we really can't handle is incorrect byte ordering. |
| */ |
| { |
| const u4 matchMask = DEX_OPT_FLAG_BIG; |
| u4 expectedFlags = 0; |
| #if __BYTE_ORDER != __LITTLE_ENDIAN |
| expectedFlags |= DEX_OPT_FLAG_BIG; |
| #endif |
| if ((expectedFlags & matchMask) != (optHdr.flags & matchMask)) { |
| LOGI("DexOpt: header flag mismatch (0x%02x vs 0x%02x, mask=0x%02x)", |
| expectedFlags, optHdr.flags, matchMask); |
| goto bail; |
| } |
| } |
| |
| posn = lseek(fd, optHdr.depsOffset, SEEK_SET); |
| if (posn < 0) { |
| LOGW("DexOpt: seek to deps failed: %s", strerror(errno)); |
| goto bail; |
| } |
| |
| /* |
| * Read all of the dependency stuff into memory. |
| */ |
| depData = (u1*) malloc(optHdr.depsLength); |
| if (depData == NULL) { |
| LOGW("DexOpt: unable to allocate %d bytes for deps", |
| optHdr.depsLength); |
| goto bail; |
| } |
| actual = read(fd, depData, optHdr.depsLength); |
| if (actual < 0) { |
| LOGW("DexOpt: failed reading deps: %s", strerror(errno)); |
| goto bail; |
| } else if (actual != (ssize_t) optHdr.depsLength) { |
| LOGW("DexOpt: failed reading deps: got %d of %d", |
| (int) actual, optHdr.depsLength); |
| goto bail; |
| } |
| |
| /* |
| * Verify simple items. |
| */ |
| const u1* ptr; |
| u4 val; |
| |
| ptr = depData; |
| val = read4LE(&ptr); |
| if (sourceAvail && val != modWhen) { |
| LOGI("DexOpt: source file mod time mismatch (%08x vs %08x)", |
| val, modWhen); |
| goto bail; |
| } |
| val = read4LE(&ptr); |
| if (sourceAvail && val != crc) { |
| LOGI("DexOpt: source file CRC mismatch (%08x vs %08x)", val, crc); |
| goto bail; |
| } |
| val = read4LE(&ptr); |
| if (val != DALVIK_VM_BUILD) { |
| LOGD("DexOpt: VM build version mismatch (%d vs %d)", |
| val, DALVIK_VM_BUILD); |
| goto bail; |
| } |
| |
| /* |
| * Verify dependencies on other cached DEX files. It must match |
| * exactly with what is currently defined in the bootclasspath. |
| */ |
| ClassPathEntry* cpe; |
| u4 numDeps; |
| |
| numDeps = read4LE(&ptr); |
| LOGV("+++ DexOpt: numDeps = %d", numDeps); |
| for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) { |
| const char* cacheFileName = |
| dvmPathToAbsolutePortion(getCacheFileName(cpe)); |
| assert(cacheFileName != NULL); /* guaranteed by Class.c */ |
| |
| const u1* signature = getSignature(cpe); |
| size_t len = strlen(cacheFileName) +1; |
| u4 storedStrLen; |
| |
| if (numDeps == 0) { |
| /* more entries in bootclasspath than in deps list */ |
| LOGI("DexOpt: not all deps represented"); |
| goto bail; |
| } |
| |
| storedStrLen = read4LE(&ptr); |
| if (len != storedStrLen || |
| strcmp(cacheFileName, (const char*) ptr) != 0) |
| { |
| LOGI("DexOpt: mismatch dep name: '%s' vs. '%s'", |
| cacheFileName, ptr); |
| goto bail; |
| } |
| |
| ptr += storedStrLen; |
| |
| if (memcmp(signature, ptr, kSHA1DigestLen) != 0) { |
| LOGI("DexOpt: mismatch dep signature for '%s'", cacheFileName); |
| goto bail; |
| } |
| ptr += kSHA1DigestLen; |
| |
| LOGV("DexOpt: dep match on '%s'", cacheFileName); |
| |
| numDeps--; |
| } |
| |
| if (numDeps != 0) { |
| /* more entries in deps list than in classpath */ |
| LOGI("DexOpt: Some deps went away"); |
| goto bail; |
| } |
| |
| // consumed all data and no more? |
| if (ptr != depData + optHdr.depsLength) { |
| LOGW("DexOpt: Spurious dep data? %d vs %d", |
| (int) (ptr - depData), optHdr.depsLength); |
| assert(false); |
| } |
| |
| result = true; |
| |
| bail: |
| free(depData); |
| return result; |
| } |
| |
| /* |
| * Write the dependency info to "fd" at the current file position. |
| */ |
| static int writeDependencies(int fd, u4 modWhen, u4 crc) |
| { |
| u1* buf = NULL; |
| int result = -1; |
| ssize_t bufLen; |
| ClassPathEntry* cpe; |
| int numDeps; |
| |
| /* |
| * Count up the number of completed entries in the bootclasspath. |
| */ |
| numDeps = 0; |
| bufLen = 0; |
| for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) { |
| const char* cacheFileName = |
| dvmPathToAbsolutePortion(getCacheFileName(cpe)); |
| assert(cacheFileName != NULL); /* guaranteed by Class.c */ |
| |
| LOGV("+++ DexOpt: found dep '%s'", cacheFileName); |
| |
| numDeps++; |
| bufLen += strlen(cacheFileName) +1; |
| } |
| |
| bufLen += 4*4 + numDeps * (4+kSHA1DigestLen); |
| |
| buf = (u1*)malloc(bufLen); |
| |
| set4LE(buf+0, modWhen); |
| set4LE(buf+4, crc); |
| set4LE(buf+8, DALVIK_VM_BUILD); |
| set4LE(buf+12, numDeps); |
| |
| // TODO: do we want to add dvmGetInlineOpsTableLength() here? Won't |
| // help us if somebody replaces an existing entry, but it'd catch |
| // additions/removals. |
| |
| u1* ptr = buf + 4*4; |
| for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) { |
| const char* cacheFileName = |
| dvmPathToAbsolutePortion(getCacheFileName(cpe)); |
| assert(cacheFileName != NULL); /* guaranteed by Class.c */ |
| |
| const u1* signature = getSignature(cpe); |
| int len = strlen(cacheFileName) +1; |
| |
| if (ptr + 4 + len + kSHA1DigestLen > buf + bufLen) { |
| LOGE("DexOpt: overran buffer"); |
| dvmAbort(); |
| } |
| |
| set4LE(ptr, len); |
| ptr += 4; |
| memcpy(ptr, cacheFileName, len); |
| ptr += len; |
| memcpy(ptr, signature, kSHA1DigestLen); |
| ptr += kSHA1DigestLen; |
| } |
| |
| assert(ptr == buf + bufLen); |
| |
| result = sysWriteFully(fd, buf, bufLen, "DexOpt dep info"); |
| |
| free(buf); |
| return result; |
| } |
| |
| |
| /* |
| * Write a block of data in "chunk" format. |
| * |
| * The chunk header fields are always in "native" byte order. If "size" |
| * is not a multiple of 8 bytes, the data area is padded out. |
| */ |
| static bool writeChunk(int fd, u4 type, const void* data, size_t size) |
| { |
| union { /* save a syscall by grouping these together */ |
| char raw[8]; |
| struct { |
| u4 type; |
| u4 size; |
| } ts; |
| } header; |
| |
| assert(sizeof(header) == 8); |
| |
| LOGV("Writing chunk, type=%.4s size=%d", (char*) &type, size); |
| |
| header.ts.type = type; |
| header.ts.size = (u4) size; |
| if (sysWriteFully(fd, &header, sizeof(header), |
| "DexOpt opt chunk header write") != 0) |
| { |
| return false; |
| } |
| |
| if (size > 0) { |
| if (sysWriteFully(fd, data, size, "DexOpt opt chunk write") != 0) |
| return false; |
| } |
| |
| /* if necessary, pad to 64-bit alignment */ |
| if ((size & 7) != 0) { |
| int padSize = 8 - (size & 7); |
| LOGV("size was %d, inserting %d pad bytes", size, padSize); |
| lseek(fd, padSize, SEEK_CUR); |
| } |
| |
| assert( ((int)lseek(fd, 0, SEEK_CUR) & 7) == 0); |
| |
| return true; |
| } |
| |
| /* |
| * Write opt data. |
| * |
| * We have different pieces, some of which may be optional. To make the |
| * most effective use of space, we use a "chunk" format, with a 4-byte |
| * type and a 4-byte length. We guarantee 64-bit alignment for the data, |
| * so it can be used directly when the file is mapped for reading. |
| */ |
| static bool writeOptData(int fd, const DexClassLookup* pClassLookup, |
| const RegisterMapBuilder* pRegMapBuilder) |
| { |
| /* pre-computed class lookup hash table */ |
| if (!writeChunk(fd, (u4) kDexChunkClassLookup, |
| pClassLookup, pClassLookup->size)) |
| { |
| return false; |
| } |
| |
| /* register maps (optional) */ |
| if (pRegMapBuilder != NULL) { |
| if (!writeChunk(fd, (u4) kDexChunkRegisterMaps, |
| pRegMapBuilder->data, pRegMapBuilder->size)) |
| { |
| return false; |
| } |
| } |
| |
| /* write the end marker */ |
| if (!writeChunk(fd, (u4) kDexChunkEnd, NULL, 0)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Compute a checksum on a piece of an open file. |
| * |
| * File will be positioned at end of checksummed area. |
| * |
| * Returns "true" on success. |
| */ |
| static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum) |
| { |
| unsigned char readBuf[8192]; |
| ssize_t actual; |
| uLong adler; |
| |
| if (lseek(fd, start, SEEK_SET) != start) { |
| LOGE("Unable to seek to start of checksum area (%ld): %s", |
| (long) start, strerror(errno)); |
| return false; |
| } |
| |
| adler = adler32(0L, Z_NULL, 0); |
| |
| while (length != 0) { |
| size_t wanted = (length < sizeof(readBuf)) ? length : sizeof(readBuf); |
| actual = read(fd, readBuf, wanted); |
| if (actual <= 0) { |
| LOGE("Read failed (%d) while computing checksum (len=%zu): %s", |
| (int) actual, length, strerror(errno)); |
| return false; |
| } |
| |
| adler = adler32(adler, readBuf, actual); |
| |
| length -= actual; |
| } |
| |
| *pSum = adler; |
| return true; |
| } |
| |
| /* |
| * Update the Adler-32 checksum stored in the DEX file. This covers the |
| * swapped and optimized DEX data, but does not include the opt header |
| * or optimized data. |
| */ |
| static void updateChecksum(u1* addr, int len, DexHeader* pHeader) |
| { |
| /* |
| * Rewrite the checksum. We leave the SHA-1 signature alone. |
| */ |
| uLong adler = adler32(0L, Z_NULL, 0); |
| const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum); |
| |
| adler = adler32(adler, addr + nonSum, len - nonSum); |
| pHeader->checksum = adler; |
| } |