/* * If the concurrent GC is running, wait for it to finish. The caller * must hold the heap lock. * * Note: the second dvmChangeStatus() could stall if we were in RUNNING * on entry, and some other thread has asked us to suspend. In that * case we will be suspended with the heap lock held, which can lead to * deadlock if the other thread tries to do something with the managed heap. * For example, the debugger might suspend us and then execute a method that * allocates memory. We can avoid this situation by releasing the lock * before self-suspending. (The developer can work around this specific * situation by single-stepping the VM. Alternatively, we could disable * concurrent GC when the debugger is attached, but that might change * behavior more than is desirable.) * * This should not be a problem in production, because any GC-related * activity will grab the lock before issuing a suspend-all. (We may briefly * suspend when the GC thread calls dvmUnlockHeap before dvmResumeAllThreads, * but there's no risk of deadlock.) */ bool dvmWaitForConcurrentGcToComplete() { ATRACE_BEGIN("GC: Wait For Concurrent"); bool waited = gDvm.gcHeap->gcRunning; Thread *self = dvmThreadSelf(); assert(self != NULL); u4 start = dvmGetRelativeTimeMsec(); #ifdef FASTIVA // Ensure no Java-object reference is used in local-stack. // and save Java-object reference maybe in registers. FASTIVA_SUSPEND_STACK_unsafe(self); ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); while (gDvm.gcHeap->gcRunning) { dvmWaitCond(&gDvm.gcHeapCond, &gDvm.gcHeapLock); } dvmChangeStatus(self, oldStatus); FASTIVA_RESUME_STACK_unsafe(self); #else while (gDvm.gcHeap->gcRunning) { ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmWaitCond(&gDvm.gcHeapCond, &gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); } #endif u4 end = dvmGetRelativeTimeMsec(); if (end - start > 0) { ALOGD("WAIT_FOR_CONCURRENT_GC blocked %ums", end - start); } ATRACE_END(); return waited; }
/* * If the concurrent GC is running, wait for it to finish. The caller * must hold the heap lock. * * Note: the second dvmChangeStatus() could stall if we were in RUNNING * on entry, and some other thread has asked us to suspend. In that * case we will be suspended with the heap lock held, which can lead to * deadlock if the other thread tries to do something with the managed heap. * For example, the debugger might suspend us and then execute a method that * allocates memory. We can avoid this situation by releasing the lock * before self-suspending. (The developer can work around this specific * situation by single-stepping the VM. Alternatively, we could disable * concurrent GC when the debugger is attached, but that might change * behavior more than is desirable.) * * This should not be a problem in production, because any GC-related * activity will grab the lock before issuing a suspend-all. (We may briefly * suspend when the GC thread calls dvmUnlockHeap before dvmResumeAllThreads, * but there's no risk of deadlock.) */ void dvmWaitForConcurrentGcToComplete() { Thread *self = dvmThreadSelf(); assert(self != NULL); u4 start = dvmGetRelativeTimeMsec(); while (gDvm.gcHeap->gcRunning) { ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmWaitCond(&gDvm.gcHeapCond, &gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); } u4 end = dvmGetRelativeTimeMsec(); ALOGD("WAIT_FOR_CONCURRENT_GC blocked %ums", end - start); }
/* * Grab the lock, but put ourselves into THREAD_VMWAIT if it looks like * we're going to have to wait on the mutex. */ bool dvmLockHeap() { if (dvmTryLockMutex(&gDvm.gcHeapLock) != 0) { Thread *self; ThreadStatus oldStatus; self = dvmThreadSelf(); oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmLockMutex(&gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); } return true; }
/* * Acquires a mutex, transitioning to the VMWAIT state if the mutex is * held. This allows the thread to suspend while it waits for another * thread to release the mutex. */ static void lockMutex(pthread_mutex_t *mu) { Thread *self; ThreadStatus oldStatus; assert(mu != NULL); if (dvmTryLockMutex(mu) != 0) { self = dvmThreadSelf(); assert(self != NULL); oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmLockMutex(mu); dvmChangeStatus(self, oldStatus); } }
/* * The garbage collection daemon. Initiates a concurrent collection * when signaled. Also periodically trims the heaps when a few seconds * have elapsed since the last concurrent GC. */ static void *gcDaemonThread(void* arg) { dvmChangeStatus(NULL, THREAD_VMWAIT); dvmLockMutex(&gHs->gcThreadMutex); while (gHs->gcThreadShutdown != true) { bool trim = false; if (gHs->gcThreadTrimNeeded) { int result = dvmRelativeCondWait(&gHs->gcThreadCond, &gHs->gcThreadMutex, HEAP_TRIM_IDLE_TIME_MS, 0); if (result == ETIMEDOUT) { /* Timed out waiting for a GC request, schedule a heap trim. */ trim = true; } } else { dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex); } // Many JDWP requests cause allocation. We can't take the heap lock and wait to // transition to runnable so we can start a GC if a debugger is connected, because // we don't know that the JDWP thread isn't about to allocate and require the // heap lock itself, leading to deadlock. http://b/8191824. if (gDvm.debuggerConnected) { continue; } dvmLockHeap(); /* * Another thread may have started a concurrent garbage * collection before we were scheduled. Check for this * condition before proceeding. */ if (!gDvm.gcHeap->gcRunning) { dvmChangeStatus(NULL, THREAD_RUNNING); if (trim) { trimHeaps(); gHs->gcThreadTrimNeeded = false; } else { dvmCollectGarbageInternal(GC_CONCURRENT); gHs->gcThreadTrimNeeded = true; } dvmChangeStatus(NULL, THREAD_VMWAIT); } dvmUnlockHeap(); } dvmChangeStatus(NULL, THREAD_RUNNING); return NULL; }
/* * private static void nativeExit(int code, boolean isExit) * * Runtime.exit() calls this after doing shutdown processing. Runtime.halt() * uses this as well. */ static void Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult) { int status = args[0]; bool isExit = (args[1] != 0); if (isExit && gDvm.exitHook != NULL) { dvmChangeStatus(NULL, THREAD_NATIVE); (*gDvm.exitHook)(status); // not expected to return dvmChangeStatus(NULL, THREAD_RUNNING); LOGW("JNI exit hook returned\n"); } LOGD("Calling exit(%d)\n", status); #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) dvmCompilerDumpStats(); #endif exit(status); }
static void* coreValuesCatcherThreadStart(void* arg) { Thread* self = dvmThreadSelf(); UNUSED_PARAMETER(arg); while (true) { //maybe have to put break somewhere dvmChangeStatus(self, THREAD_VMWAIT); dvmChangeStatus(self, THREAD_RUNNING); dvmSystemCoreValuesUpdate(); /*Update each second*/ dvmThreadSleep(1000, 0); } return NULL; }
/* * Grab the lock, but put ourselves into THREAD_VMWAIT if it looks like * we're going to have to wait on the mutex. */ bool dvmLockHeap() { /* This is hacked a bit to avoid deadlocks. Basically I don't want a thread * to suspend itself hodling the heap lock. */ int res; while ((res = dvmTryLockMutex(&gDvm.gcHeapLock)) != 0) { assert(res == EBUSY); Thread *self; ThreadStatus oldStatus; self = dvmThreadSelf(); oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmLockMutex(&gDvm.gcHeapLock); dvmUnlockMutex(&gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); } return true; }
/* * Block until all pending heap worker work has finished. */ void dvmWaitForHeapWorkerIdle() { assert(gDvm.heapWorkerReady); dvmChangeStatus(NULL, THREAD_VMWAIT); dvmLockMutex(&gDvm.heapWorkerLock); /* Wake up the heap worker and wait for it to finish. */ //TODO(http://b/issue?id=699704): This will deadlock if // called from finalize(), enqueue(), or clear(). We // need to detect when this is called from the HeapWorker // context and just give up. dvmSignalHeapWorker(false); dvmWaitCond(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock); dvmUnlockMutex(&gDvm.heapWorkerLock); dvmChangeStatus(NULL, THREAD_RUNNING); }
/* * Grab the lock, but put ourselves into THREAD_VMWAIT if it looks like * we're going to have to wait on the mutex. */ bool dvmLockHeap() { if (dvmTryLockMutex(&gDvm.gcHeapLock) != 0) { Thread *self; ThreadStatus oldStatus; self = dvmThreadSelf(); #ifdef FASTIVA FASTIVA_SUSPEND_STACK_unsafe(self); #endif oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmLockMutex(&gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); #ifdef FASTIVA FASTIVA_RESUME_STACK_unsafe(self); #endif } return true; }
/* * If the concurrent GC is running, wait for it to finish. The caller * must hold the heap lock. * * Note: the second dvmChangeStatus() could stall if we were in RUNNING * on entry, and some other thread has asked us to suspend. In that * case we will be suspended with the heap lock held, which can lead to * deadlock if the other thread tries to do something with the managed heap. * For example, the debugger might suspend us and then execute a method that * allocates memory. We can avoid this situation by releasing the lock * before self-suspending. (The developer can work around this specific * situation by single-stepping the VM. Alternatively, we could disable * concurrent GC when the debugger is attached, but that might change * behavior more than is desirable.) * * This should not be a problem in production, because any GC-related * activity will grab the lock before issuing a suspend-all. (We may briefly * suspend when the GC thread calls dvmUnlockHeap before dvmResumeAllThreads, * but there's no risk of deadlock.) */ bool dvmWaitForConcurrentGcToComplete() { bool waited = gDvm.gcHeap->gcRunning; Thread *self = dvmThreadSelf(); assert(self != NULL); #ifdef DEBUG u4 start = dvmGetRelativeTimeMsec(); #endif while (gDvm.gcHeap->gcRunning) { ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmWaitCond(&gDvm.gcHeapCond, &gDvm.gcHeapLock); dvmChangeStatus(self, oldStatus); } #ifdef DEBUG u4 end = dvmGetRelativeTimeMsec(); if (end - start > 0) { ALOGD("WAIT_FOR_CONCURRENT_GC blocked %ums", end - start); } #endif return waited; }
/* * The garbage collection daemon. Initiates a concurrent collection * when signaled. Also periodically trims the heaps when a few seconds * have elapsed since the last concurrent GC. */ static void *gcDaemonThread(void* arg) { dvmChangeStatus(NULL, THREAD_VMWAIT); dvmLockMutex(&gHs->gcThreadMutex); while (gHs->gcThreadShutdown != true) { bool trim = false; if (gHs->gcThreadTrimNeeded) { int result = dvmRelativeCondWait(&gHs->gcThreadCond, &gHs->gcThreadMutex, HEAP_TRIM_IDLE_TIME_MS, 0); if (result == ETIMEDOUT) { /* Timed out waiting for a GC request, schedule a heap trim. */ trim = true; } } else { dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex); } dvmLockHeap(); /* * Another thread may have started a concurrent garbage * collection before we were scheduled. Check for this * condition before proceeding. */ if (!gDvm.gcHeap->gcRunning) { dvmChangeStatus(NULL, THREAD_RUNNING); if (trim) { trimHeaps(); gHs->gcThreadTrimNeeded = false; } else { dvmCollectGarbageInternal(GC_CONCURRENT); gHs->gcThreadTrimNeeded = true; } dvmChangeStatus(NULL, THREAD_VMWAIT); } dvmUnlockHeap(); } dvmChangeStatus(NULL, THREAD_RUNNING); return NULL; }
// simplified copy of Method.invokeNative, but calls the original (non-hooked) method and has no access checks // used when a method has been hooked static jobject miui_dexspy_DexspyInstaller_invokeOriginalMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethod, jobjectArray params1, jclass returnType1, jobject thisObject1, jobjectArray args1) { // try to find the original method Method* method = (Method*)env->FromReflectedMethod(reflectedMethod); OriginalMethodsIt original = findOriginalMethod(method); if (original != dexspyOriginalMethods.end()) { method = &(*original); } // dereference parameters ::Thread* self = dvmThreadSelf(); Object* thisObject = dvmDecodeIndirectRef(self, thisObject1); ArrayObject* args = (ArrayObject*)dvmDecodeIndirectRef(self, args1); ArrayObject* params = (ArrayObject*)dvmDecodeIndirectRef(self, params1); ClassObject* returnType = (ClassObject*)dvmDecodeIndirectRef(self, returnType1); // invoke the method dvmChangeStatus(self, THREAD_RUNNING); Object* result = dvmInvokeMethod(thisObject, method, args, params, returnType, true); dvmChangeStatus(self, THREAD_NATIVE); return dexspyAddLocalReference(self, result); }
/* * Wait on a monitor until timeout, interrupt, or notification. Used for * Object.wait() and (somewhat indirectly) Thread.sleep() and Thread.join(). * * If another thread calls Thread.interrupt(), we throw InterruptedException * and return immediately if one of the following are true: * - blocked in wait(), wait(long), or wait(long, int) methods of Object * - blocked in join(), join(long), or join(long, int) methods of Thread * - blocked in sleep(long), or sleep(long, int) methods of Thread * Otherwise, we set the "interrupted" flag. * * Checks to make sure that "nsec" is in the range 0-999999 * (i.e. fractions of a millisecond) and throws the appropriate * exception if it isn't. * * The spec allows "spurious wakeups", and recommends that all code using * Object.wait() do so in a loop. This appears to derive from concerns * about pthread_cond_wait() on multiprocessor systems. Some commentary * on the web casts doubt on whether these can/should occur. * * Since we're allowed to wake up "early", we clamp extremely long durations * to return at the end of the 32-bit time epoch. */ static void waitMonitor(Thread* self, Monitor* mon, s8 msec, s4 nsec, bool interruptShouldThrow) { struct timespec ts; bool wasInterrupted = false; bool timed; int ret; assert(self != NULL); assert(mon != NULL); /* Make sure that we hold the lock. */ if (mon->owner != self) { dvmThrowIllegalMonitorStateException( "object not locked by thread before wait()"); return; } /* * Enforce the timeout range. */ if (msec < 0 || nsec < 0 || nsec > 999999) { dvmThrowIllegalArgumentException("timeout arguments out of range"); return; } /* * Compute absolute wakeup time, if necessary. */ if (msec == 0 && nsec == 0) { timed = false; } else { absoluteTime(msec, nsec, &ts); timed = true; } /* * Add ourselves to the set of threads waiting on this monitor, and * release our hold. We need to let it go even if we're a few levels * deep in a recursive lock, and we need to restore that later. * * We append to the wait set ahead of clearing the count and owner * fields so the subroutine can check that the calling thread owns * the monitor. Aside from that, the order of member updates is * not order sensitive as we hold the pthread mutex. */ waitSetAppend(mon, self); int prevLockCount = mon->lockCount; mon->lockCount = 0; mon->owner = NULL; const Method* savedMethod = mon->ownerMethod; u4 savedPc = mon->ownerPc; mon->ownerMethod = NULL; mon->ownerPc = 0; /* * Update thread status. If the GC wakes up, it'll ignore us, knowing * that we won't touch any references in this state, and we'll check * our suspend mode before we transition out. */ if (timed) dvmChangeStatus(self, THREAD_TIMED_WAIT); else dvmChangeStatus(self, THREAD_WAIT); dvmLockMutex(&self->waitMutex); /* * Set waitMonitor to the monitor object we will be waiting on. * When waitMonitor is non-NULL a notifying or interrupting thread * must signal the thread's waitCond to wake it up. */ assert(self->waitMonitor == NULL); self->waitMonitor = mon; /* * Handle the case where the thread was interrupted before we called * wait(). */ if (self->interrupted) { wasInterrupted = true; self->waitMonitor = NULL; dvmUnlockMutex(&self->waitMutex); goto done; } /* * Release the monitor lock and wait for a notification or * a timeout to occur. */ dvmUnlockMutex(&mon->lock); if (!timed) { ret = pthread_cond_wait(&self->waitCond, &self->waitMutex); assert(ret == 0); } else { #ifdef HAVE_TIMEDWAIT_MONOTONIC ret = pthread_cond_timedwait_monotonic(&self->waitCond, &self->waitMutex, &ts); #else ret = pthread_cond_timedwait(&self->waitCond, &self->waitMutex, &ts); #endif assert(ret == 0 || ret == ETIMEDOUT); } if (self->interrupted) { wasInterrupted = true; } self->interrupted = false; self->waitMonitor = NULL; dvmUnlockMutex(&self->waitMutex); /* Reacquire the monitor lock. */ lockMonitor(self, mon); done: /* * We remove our thread from wait set after restoring the count * and owner fields so the subroutine can check that the calling * thread owns the monitor. Aside from that, the order of member * updates is not order sensitive as we hold the pthread mutex. */ mon->owner = self; mon->lockCount = prevLockCount; mon->ownerMethod = savedMethod; mon->ownerPc = savedPc; waitSetRemove(mon, self); /* set self->status back to THREAD_RUNNING, and self-suspend if needed */ dvmChangeStatus(self, THREAD_RUNNING); if (wasInterrupted) { /* * We were interrupted while waiting, or somebody interrupted an * un-interruptible thread earlier and we're bailing out immediately. * * The doc sayeth: "The interrupted status of the current thread is * cleared when this exception is thrown." */ self->interrupted = false; if (interruptShouldThrow) { dvmThrowInterruptedException(NULL); } } }
/* * The heap worker thread sits quietly until the GC tells it there's work * to do. */ static void* heapWorkerThreadStart(void* arg) { Thread *self = dvmThreadSelf(); int cc; UNUSED_PARAMETER(arg); LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId); /* tell the main thread that we're ready */ dvmLockMutex(&gDvm.heapWorkerLock); gDvm.heapWorkerReady = true; cc = pthread_cond_signal(&gDvm.heapWorkerCond); assert(cc == 0); dvmUnlockMutex(&gDvm.heapWorkerLock); dvmLockMutex(&gDvm.heapWorkerLock); while (!gDvm.haltHeapWorker) { struct timespec trimtime; bool timedwait = false; /* We're done running interpreted code for now. */ dvmChangeStatus(NULL, THREAD_VMWAIT); /* Signal anyone who wants to know when we're done. */ cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond); assert(cc == 0); /* Trim the heap if we were asked to. */ trimtime = gDvm.gcHeap->heapWorkerNextTrim; if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) { struct timespec now; #ifdef HAVE_TIMEDWAIT_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &now); // relative time #else struct timeval tvnow; gettimeofday(&tvnow, NULL); // absolute time now.tv_sec = tvnow.tv_sec; now.tv_nsec = tvnow.tv_usec * 1000; #endif if (trimtime.tv_sec < now.tv_sec || (trimtime.tv_sec == now.tv_sec && trimtime.tv_nsec <= now.tv_nsec)) { size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT]; /* The heap must be locked before the HeapWorker; * unroll and re-order the locks. dvmLockHeap() * will put us in VMWAIT if necessary. Once it * returns, there shouldn't be any contention on * heapWorkerLock. */ dvmUnlockMutex(&gDvm.heapWorkerLock); dvmLockHeap(); dvmLockMutex(&gDvm.heapWorkerLock); memset(madvisedSizes, 0, sizeof(madvisedSizes)); dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); dvmUnlockHeap(); trimtime.tv_sec = 0; trimtime.tv_nsec = 0; gDvm.gcHeap->heapWorkerNextTrim = trimtime; } else { timedwait = true; } } /* sleep until signaled */ if (timedwait) { #ifdef HAVE_TIMEDWAIT_MONOTONIC cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock, &trimtime); #else cc = pthread_cond_timedwait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock, &trimtime); #endif assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR); } else { cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); assert(cc == 0); } /* dvmChangeStatus() may block; don't hold heapWorkerLock. */ dvmUnlockMutex(&gDvm.heapWorkerLock); dvmChangeStatus(NULL, THREAD_RUNNING); dvmLockMutex(&gDvm.heapWorkerLock); LOGV("HeapWorker is awake\n"); /* Process any events in the queue. */ doHeapWork(self); } dvmUnlockMutex(&gDvm.heapWorkerLock); if (gDvm.verboseShutdown) LOGD("HeapWorker thread shutting down\n"); return NULL; }
static void dexspyCallHandler(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { OriginalMethodsIt original = findOriginalMethod(method); if (original == dexspyOriginalMethods.end()) { dvmThrowNoSuchMethodError("could not find Dexspy original method - how did you even get here?"); return; } ThreadStatus oldThreadStatus = self->status; JNIEnv* env = self->jniEnv; // get java.lang.reflect.Method object for original method jobject originalReflected = env->ToReflectedMethod( (jclass)dexspyAddLocalReference(self, original->clazz), (jmethodID)method, true); // convert/box arguments const char* desc = &method->shorty[1]; // [0] is the return type. Object* thisObject = NULL; size_t srcIndex = 0; size_t dstIndex = 0; // for non-static methods determine the "this" pointer if (!dvmIsStaticMethod(&(*original))) { thisObject = (Object*) dexspyAddLocalReference(self, (Object*)args[0]); srcIndex++; } jclass objectClass = env->FindClass("java/lang/Object"); jobjectArray argsArray = env->NewObjectArray(strlen(method->shorty) - 1, objectClass, NULL); while (*desc != '\0') { char descChar = *(desc++); JValue value; Object* obj; switch (descChar) { case 'Z': case 'C': case 'F': case 'B': case 'S': case 'I': value.i = args[srcIndex++]; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, NULL); break; case 'D': case 'J': value.j = dvmGetArgLong(args, srcIndex); srcIndex += 2; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, NULL); break; case '[': case 'L': obj = (Object*) args[srcIndex++]; break; default: ALOGE("Unknown method signature description character: %c\n", descChar); obj = NULL; srcIndex++; } env->SetObjectArrayElement(argsArray, dstIndex++, dexspyAddLocalReference(self, obj)); } // call the Java handler function jobject resultRef = env->CallStaticObjectMethod( dexspyClass, dexspyHandleHookedMethod, originalReflected, thisObject, argsArray); // exceptions are thrown to the caller if (env->ExceptionCheck()) { dvmChangeStatus(self, oldThreadStatus); return; } // return result with proper type Object* result = dvmDecodeIndirectRef(self, resultRef); ClassObject* returnType = dvmGetBoxedReturnType(method); if (returnType->primitiveType == PRIM_VOID) { // ignored } else if (result == NULL) { if (dvmIsPrimitiveClass(returnType)) { dvmThrowNullPointerException("null result when primitive expected"); } pResult->l = NULL; } else { if (!dvmUnboxPrimitive(result, returnType, pResult)) { dvmThrowClassCastException(result->clazz, returnType); } } // set the thread status back to running. must be done after the last env->...() dvmChangeStatus(self, oldThreadStatus); }
/* * 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) ---\n", 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'\n", 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); char* argv[argc+1]; // last entry is NULL char values[argc][kMaxIntLen]; char* execFile; 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\n"); androidRoot = "/system"; } execFile = malloc(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, argv); else execv(execFile, argv); LOGE("execv '%s'%s failed: %s\n", execFile, kUseValgrind ? " [valgrind]" : "", strerror(errno)); exit(1); } else { LOGV("DexOpt: waiting for verify+opt, pid=%d\n", (int) pid); int status; pid_t gotPid; int oldStatus; /* * Wait for the optimization process to finish. We go into VMWAIT * mode here so GC suspension won't have to wait for us. */ oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); while (true) { gotPid = waitpid(pid, &status, 0); if (gotPid == -1 && errno == EINTR) { LOGD("waitpid interrupted, retrying\n"); } else { break; } } dvmChangeStatus(NULL, oldStatus); if (gotPid != pid) { LOGE("waitpid failed: wanted %d, got %d: %s\n", (int) pid, (int) gotPid, strerror(errno)); return false; } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { LOGD("DexOpt: --- END '%s' (success) ---\n", lastPart); return true; } else { LOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed\n", lastPart, status); return false; } } }
/* * The heap worker thread sits quietly until the GC tells it there's work * to do. */ static void* heapWorkerThreadStart(void* arg) { Thread *self = dvmThreadSelf(); UNUSED_PARAMETER(arg); LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId); /* tell the main thread that we're ready */ lockMutex(&gDvm.heapWorkerLock); gDvm.heapWorkerReady = true; dvmSignalCond(&gDvm.heapWorkerCond); dvmUnlockMutex(&gDvm.heapWorkerLock); lockMutex(&gDvm.heapWorkerLock); while (!gDvm.haltHeapWorker) { struct timespec trimtime; bool timedwait = false; /* We're done running interpreted code for now. */ dvmChangeStatus(NULL, THREAD_VMWAIT); /* Signal anyone who wants to know when we're done. */ dvmBroadcastCond(&gDvm.heapWorkerIdleCond); /* Trim the heap if we were asked to. */ trimtime = gDvm.gcHeap->heapWorkerNextTrim; if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) { struct timespec now; #ifdef HAVE_TIMEDWAIT_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &now); // relative time #else struct timeval tvnow; gettimeofday(&tvnow, NULL); // absolute time now.tv_sec = tvnow.tv_sec; now.tv_nsec = tvnow.tv_usec * 1000; #endif if (trimtime.tv_sec < now.tv_sec || (trimtime.tv_sec == now.tv_sec && trimtime.tv_nsec <= now.tv_nsec)) { size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT]; /* * Acquire the gcHeapLock. The requires releasing the * heapWorkerLock before the gcHeapLock is acquired. * It is possible that the gcHeapLock may be acquired * during a concurrent GC in which case heapWorkerLock * is held by the GC and we are unable to make forward * progress. We avoid deadlock by releasing the * gcHeapLock and then waiting to be signaled when the * GC completes. There is no guarantee that the next * time we are run will coincide with GC inactivity so * the check and wait must be performed within a loop. */ dvmUnlockMutex(&gDvm.heapWorkerLock); dvmLockHeap(); while (gDvm.gcHeap->gcRunning) { dvmWaitForConcurrentGcToComplete(); } dvmLockMutex(&gDvm.heapWorkerLock); memset(madvisedSizes, 0, sizeof(madvisedSizes)); dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT); dvmUnlockHeap(); trimtime.tv_sec = 0; trimtime.tv_nsec = 0; gDvm.gcHeap->heapWorkerNextTrim = trimtime; } else { timedwait = true; } } /* sleep until signaled */ if (timedwait) { int cc __attribute__ ((__unused__)); #ifdef HAVE_TIMEDWAIT_MONOTONIC cc = pthread_cond_timedwait_monotonic(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock, &trimtime); #else cc = pthread_cond_timedwait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock, &trimtime); #endif assert(cc == 0 || cc == ETIMEDOUT); } else { dvmWaitCond(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock); } /* * Return to the running state before doing heap work. This * will block if the GC has initiated a suspend. We release * the heapWorkerLock beforehand for the GC to make progress * and wait to be signaled after the GC completes. There is * no guarantee that the next time we are run will coincide * with GC inactivity so the check and wait must be performed * within a loop. */ dvmUnlockMutex(&gDvm.heapWorkerLock); dvmChangeStatus(NULL, THREAD_RUNNING); dvmLockHeap(); while (gDvm.gcHeap->gcRunning) { dvmWaitForConcurrentGcToComplete(); } dvmLockMutex(&gDvm.heapWorkerLock); dvmUnlockHeap(); LOGV("HeapWorker is awake\n"); /* Process any events in the queue. */ doHeapWork(self); } dvmUnlockMutex(&gDvm.heapWorkerLock); if (gDvm.verboseShutdown) LOGD("HeapWorker thread shutting down\n"); return NULL; }
/* * Select on stdout/stderr pipes, waiting for activity. * * DO NOT use printf from here. */ static void* stdioConverterThreadStart(void* arg) { int cc; #ifdef _WIN32 _asm int 3; #else /* tell the main thread that we're ready */ dvmLockMutex(&gDvm.stdioConverterLock); gDvm.stdioConverterReady = true; cc = pthread_cond_signal(&gDvm.stdioConverterCond); assert(cc == 0); dvmUnlockMutex(&gDvm.stdioConverterLock); /* we never do anything that affects the rest of the VM */ dvmChangeStatus(NULL, THREAD_VMWAIT); /* * Allocate read buffers. */ BufferedData* stdoutData = new BufferedData; BufferedData* stderrData = new BufferedData; stdoutData->count = stderrData->count = 0; /* * Read until shutdown time. */ while (!gDvm.haltStdioConverter) { fd_set readfds; int maxFd, fdCount; FD_ZERO(&readfds); FD_SET(gDvm.stdoutPipe[0], &readfds); FD_SET(gDvm.stderrPipe[0], &readfds); maxFd = MAX(gDvm.stdoutPipe[0], gDvm.stderrPipe[0]); fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL); if (fdCount < 0) { if (errno != EINTR) { ALOGE("select on stdout/stderr failed"); break; } ALOGD("Got EINTR, ignoring"); } else if (fdCount == 0) { ALOGD("WEIRD: select returned zero"); } else { bool err = false; if (FD_ISSET(gDvm.stdoutPipe[0], &readfds)) { err |= !readAndLog(gDvm.stdoutPipe[0], stdoutData, "stdout"); } if (FD_ISSET(gDvm.stderrPipe[0], &readfds)) { err |= !readAndLog(gDvm.stderrPipe[0], stderrData, "stderr"); } /* probably EOF; give up */ if (err) { ALOGW("stdio converter got read error; shutting it down"); break; } } } close(gDvm.stdoutPipe[0]); close(gDvm.stderrPipe[0]); delete stdoutData; delete stderrData; /* change back for shutdown sequence */ dvmChangeStatus(NULL, THREAD_RUNNING); #endif return NULL; }
/* * 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) { LOGE("Can't open dex cache '%s': %s\n", cacheFileName, strerror(errno)); } return fd; } readOnly = true; } /* * 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. */ int oldStatus; LOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)\n", cacheFileName, fd, isBootstrap); oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); cc = flock(fd, LOCK_EX | LOCK_NB); if (cc != 0) { LOGD("DexOpt: sleeping on flock(%s)\n", cacheFileName); cc = flock(fd, LOCK_EX); } dvmChangeStatus(NULL, oldStatus); if (cc != 0) { LOGE("Can't lock dex cache '%s': %d\n", cacheFileName, cc); close(fd); return -1; } LOGV("DexOpt: locked cache file\n"); /* * 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'\n", cacheFileName); LOGVV("DexOpt: unlocking cache file %s\n", 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\n"); LOGVV("DexOpt: unlocking cache file %s\n", 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\n"); goto close_fail; } cc = dexOptCreateEmptyHeader(fd); if (cc != 0) goto close_fail; *pNewFile = true; LOGV("DexOpt: successfully initialized new cache file\n"); } 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) expectOpt = expectVerify; else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ expectOpt = true; LOGV("checking deps, expecting vfy=%d opt=%d\n", 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\n", 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)\n", cacheFileName); if (ftruncate(fd, 0) != 0) { LOGW("Warning: unable to truncate cache file '%s': %s\n", cacheFileName, strerror(errno)); /* keep going */ } if (unlink(cacheFileName) != 0) { LOGW("Warning: unable to remove cache file '%s': %d %s\n", cacheFileName, errno, strerror(errno)); /* keep going; permission failure should probably be fatal */ } LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName); flock(fd, LOCK_UN); close(fd); goto retry; } else { LOGV("DexOpt: good deps in cache file\n"); } } assert(fd >= 0); return fd; close_fail: flock(fd, LOCK_UN); close(fd); return -1; }
/* * Sleep in sigwait() until a signal arrives. */ static void* signalCatcherThreadStart(void* arg) { Thread* self = dvmThreadSelf(); sigset_t mask; int cc; UNUSED_PARAMETER(arg); ALOGV("Signal catcher thread started (threadid=%d)", self->threadId); /* set up mask with signals we want to handle */ sigemptyset(&mask); sigaddset(&mask, SIGQUIT); sigaddset(&mask, SIGUSR1); #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) sigaddset(&mask, SIGUSR2); #endif while (true) { int rcvd; dvmChangeStatus(self, THREAD_VMWAIT); /* * Signals for sigwait() must be blocked but not ignored. We * block signals like SIGQUIT for all threads, so the condition * is met. When the signal hits, we wake up, without any signal * handlers being invoked. * * When running under GDB we occasionally return from sigwait() * with EINTR (e.g. when other threads exit). */ loop: cc = sigwait(&mask, &rcvd); if (cc != 0) { if (cc == EINTR) { //ALOGV("sigwait: EINTR"); goto loop; } assert(!"bad result from sigwait"); } if (!gDvm.haltSignalCatcher) { ALOGI("threadid=%d: reacting to signal %d", dvmThreadSelf()->threadId, rcvd); } /* set our status to RUNNING, self-suspending if GC in progress */ dvmChangeStatus(self, THREAD_RUNNING); if (gDvm.haltSignalCatcher) break; switch (rcvd) { case SIGQUIT: handleSigQuit(); break; case SIGUSR1: handleSigUsr1(); break; #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) case SIGUSR2: handleSigUsr2(); break; #endif default: ALOGE("unexpected signal %d", rcvd); break; } } return NULL; }
/* * Respond to a SIGQUIT by dumping the thread stacks. Optionally dump * a few other things while we're at it. * * Thread stacks can either go to the log or to a file designated for holding * ANR traces. If we're writing to a file, we want to do it in one shot, * so we can use a single O_APPEND write instead of contending for exclusive * access with flock(). There may be an advantage in resuming the VM * before doing the file write, so we don't stall the VM if disk I/O is * bottlenecked. * * If JIT tuning is compiled in, dump compiler stats as well. */ static void handleSigQuit() { char* traceBuf = NULL; size_t traceLen; dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP); dvmDumpLoaderStats("sig"); if (gDvm.stackTraceFile == NULL) { /* just dump to log */ DebugOutputTarget target; dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG); dvmDumpJniStats(&target); dvmDumpAllThreadsEx(&target, true); } else { /* write to memory buffer */ FILE* memfp = open_memstream(&traceBuf, &traceLen); if (memfp == NULL) { ALOGE("Unable to create memstream for stack traces"); traceBuf = NULL; /* make sure it didn't touch this */ /* continue on */ } else { logThreadStacks(memfp); fclose(memfp); } } #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) dvmCompilerDumpStats(); #endif if (false) dvmDumpTrackedAllocations(true); dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP); if (traceBuf != NULL) { /* * We don't know how long it will take to do the disk I/O, so put us * into VMWAIT for the duration. */ ThreadStatus oldStatus = dvmChangeStatus(dvmThreadSelf(), THREAD_VMWAIT); /* * Open the stack trace output file, creating it if necessary. It * needs to be world-writable so other processes can write to it. */ int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd < 0) { ALOGE("Unable to open stack trace file '%s': %s", gDvm.stackTraceFile, strerror(errno)); } else { ssize_t actual = write(fd, traceBuf, traceLen); if (actual != (ssize_t) traceLen) { ALOGE("Failed to write stack traces to %s (%d of %zd): %s", gDvm.stackTraceFile, (int) actual, traceLen, strerror(errno)); } else { ALOGI("Wrote stack traces to '%s'", gDvm.stackTraceFile); } close(fd); } free(traceBuf); dvmChangeStatus(dvmThreadSelf(), oldStatus); } }
static void *compilerThreadStart(void *arg) { dvmChangeStatus(NULL, THREAD_VMWAIT); /* * If we're not running stand-alone, wait a little before * recieving translation requests on the assumption that process start * up code isn't worth compiling. We'll resume when the framework * signals us that the first screen draw has happened, or the timer * below expires (to catch daemons). * * There is a theoretical race between the callback to * VMRuntime.startJitCompiation and when the compiler thread reaches this * point. In case the callback happens earlier, in order not to permanently * hold the system_server (which is not using the timed wait) in * interpreter-only mode we bypass the delay here. */ if (gDvmJit.runningInAndroidFramework && !gDvmJit.alreadyEnabledViaFramework) { /* * If the current VM instance is the system server (detected by having * 0 in gDvm.systemServerPid), we will use the indefinite wait on the * conditional variable to determine whether to start the JIT or not. * If the system server detects that the whole system is booted in * safe mode, the conditional variable will never be signaled and the * system server will remain in the interpreter-only mode. All * subsequent apps will be started with the --enable-safemode flag * explicitly appended. */ if (gDvm.systemServerPid == 0) { dvmLockMutex(&gDvmJit.compilerLock); pthread_cond_wait(&gDvmJit.compilerQueueActivity, &gDvmJit.compilerLock); dvmUnlockMutex(&gDvmJit.compilerLock); ALOGD("JIT started for system_server"); } else { dvmLockMutex(&gDvmJit.compilerLock); /* * TUNING: experiment with the delay & perhaps make it * target-specific */ dvmRelativeCondWait(&gDvmJit.compilerQueueActivity, &gDvmJit.compilerLock, 3000, 0); dvmUnlockMutex(&gDvmJit.compilerLock); } if (gDvmJit.haltCompilerThread) { return NULL; } } compilerThreadStartup(); dvmLockMutex(&gDvmJit.compilerLock); /* * Since the compiler thread will not touch any objects on the heap once * being created, we just fake its state as VMWAIT so that it can be a * bit late when there is suspend request pending. */ while (!gDvmJit.haltCompilerThread) { if (workQueueLength() == 0) { int cc; cc = pthread_cond_signal(&gDvmJit.compilerQueueEmpty); assert(cc == 0); pthread_cond_wait(&gDvmJit.compilerQueueActivity, &gDvmJit.compilerLock); continue; } else { do { CompilerWorkOrder work = workDequeue(); dvmUnlockMutex(&gDvmJit.compilerLock); #if defined(WITH_JIT_TUNING) /* * This is live across setjmp(). Mark it volatile to suppress * a gcc warning. We should not need this since it is assigned * only once but gcc is not smart enough. */ volatile u8 startTime = dvmGetRelativeTimeUsec(); #endif /* * Check whether there is a suspend request on me. This * is necessary to allow a clean shutdown. * * However, in the blocking stress testing mode, let the * compiler thread continue doing compilations to unblock * other requesting threads. This may occasionally cause * shutdown from proceeding cleanly in the standalone invocation * of the vm but this should be acceptable. */ if (!gDvmJit.blockingMode) dvmCheckSuspendPending(dvmThreadSelf()); /* Is JitTable filling up? */ if (gDvmJit.jitTableEntriesUsed > (gDvmJit.jitTableSize - gDvmJit.jitTableSize/4)) { bool resizeFail = dvmJitResizeJitTable(gDvmJit.jitTableSize * 2); /* * If the jit table is full, consider it's time to reset * the code cache too. */ gDvmJit.codeCacheFull |= resizeFail; } if (gDvmJit.haltCompilerThread) { ALOGD("Compiler shutdown in progress - discarding request"); } else if (!gDvmJit.codeCacheFull) { jmp_buf jmpBuf; work.bailPtr = &jmpBuf; bool aborted = setjmp(jmpBuf); if (!aborted) { bool codeCompiled = dvmCompilerDoWork(&work); /* * Make sure we are still operating with the * same translation cache version. See * Issue 4271784 for details. */ dvmLockMutex(&gDvmJit.compilerLock); if ((work.result.cacheVersion == gDvmJit.cacheVersion) && codeCompiled && !work.result.discardResult && work.result.codeAddress) { dvmJitSetCodeAddr(work.pc, work.result.codeAddress, work.result.instructionSet, false, /* not method entry */ work.result.profileCodeSize); } dvmUnlockMutex(&gDvmJit.compilerLock); } dvmCompilerArenaReset(); } free(work.info); #if defined(WITH_JIT_TUNING) gDvmJit.jitTime += dvmGetRelativeTimeUsec() - startTime; #endif dvmLockMutex(&gDvmJit.compilerLock); } while (workQueueLength() != 0); } } pthread_cond_signal(&gDvmJit.compilerQueueEmpty); dvmUnlockMutex(&gDvmJit.compilerLock); /* * As part of detaching the thread we need to call into Java code to update * the ThreadGroup, and we should not be in VMWAIT state while executing * interpreted code. */ dvmChangeStatus(NULL, THREAD_RUNNING); if (gDvm.verboseShutdown) ALOGD("Compiler thread shutting down"); return NULL; }
/* * Sleep in sigwait() until a signal arrives. */ static void* signalCatcherThreadStart(void* arg) { Thread* self = dvmThreadSelf(); sigset_t mask; int cc; UNUSED_PARAMETER(arg); LOGV("Signal catcher thread started (threadid=%d)\n", self->threadId); /* set up mask with signals we want to handle */ sigemptyset(&mask); sigaddset(&mask, SIGQUIT); sigaddset(&mask, SIGUSR1); while (true) { int rcvd; dvmChangeStatus(self, THREAD_VMWAIT); /* * Signals for sigwait() must be blocked but not ignored. We * block signals like SIGQUIT for all threads, so the condition * is met. When the signal hits, we wake up, without any signal * handlers being invoked. * * We want to suspend all other threads, so that it's safe to * traverse their stacks. * * When running under GDB we occasionally return with EINTR (e.g. * when other threads exit). */ loop: cc = sigwait(&mask, &rcvd); if (cc != 0) { if (cc == EINTR) { //LOGV("sigwait: EINTR\n"); goto loop; } assert(!"bad result from sigwait"); } if (!gDvm.haltSignalCatcher) { LOGI("threadid=%d: reacting to signal %d\n", dvmThreadSelf()->threadId, rcvd); } /* set our status to RUNNING, self-suspending if GC in progress */ dvmChangeStatus(self, THREAD_RUNNING); if (gDvm.haltSignalCatcher) break; if (rcvd == SIGQUIT) { dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP); dvmDumpLoaderStats("sig"); logThreadStacks(); if (false) { dvmLockMutex(&gDvm.jniGlobalRefLock); dvmDumpReferenceTable(&gDvm.jniGlobalRefTable, "JNI global"); dvmUnlockMutex(&gDvm.jniGlobalRefLock); } //dvmDumpTrackedAllocations(true); dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP); } else if (rcvd == SIGUSR1) { #if WITH_HPROF LOGI("SIGUSR1 forcing GC and HPROF dump\n"); hprofDumpHeap(NULL); #else LOGI("SIGUSR1 forcing GC (no HPROF)\n"); dvmCollectGarbage(false); #endif } else { LOGE("unexpected signal %d\n", rcvd); } } return NULL; }
/* * Initialize JDWP. * * Does not return until JDWP thread is running, but may return before * the thread is accepting network connections. */ JdwpState* dvmJdwpStartup(const JdwpStartupParams* pParams) { JdwpState* state = NULL; /* comment this out when debugging JDWP itself */ android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG); state = (JdwpState*) calloc(1, sizeof(JdwpState)); state->params = *pParams; state->requestSerial = 0x10000000; state->eventSerial = 0x20000000; dvmDbgInitMutex(&state->threadStartLock); dvmDbgInitMutex(&state->attachLock); dvmDbgInitMutex(&state->serialLock); dvmDbgInitMutex(&state->eventLock); state->eventThreadId = 0; dvmDbgInitMutex(&state->eventThreadLock); dvmDbgInitCond(&state->threadStartCond); dvmDbgInitCond(&state->attachCond); dvmDbgInitCond(&state->eventThreadCond); switch (pParams->transport) { case kJdwpTransportSocket: // LOGD("prepping for JDWP over TCP\n"); state->transport = dvmJdwpSocketTransport(); break; case kJdwpTransportAndroidAdb: // LOGD("prepping for JDWP over ADB\n"); state->transport = dvmJdwpAndroidAdbTransport(); /* TODO */ break; default: LOGE("Unknown transport %d\n", pParams->transport); assert(false); goto fail; } if (!dvmJdwpNetStartup(state, pParams)) goto fail; /* * Grab a mutex or two before starting the thread. This ensures they * won't signal the cond var before we're waiting. */ dvmDbgLockMutex(&state->threadStartLock); if (pParams->suspend) dvmDbgLockMutex(&state->attachLock); /* * We have bound to a port, or are trying to connect outbound to a * debugger. Create the JDWP thread and let it continue the mission. */ if (!dvmCreateInternalThread(&state->debugThreadHandle, "JDWP", jdwpThreadStart, state)) { /* state is getting tossed, but unlock these anyway for cleanliness */ dvmDbgUnlockMutex(&state->threadStartLock); if (pParams->suspend) dvmDbgUnlockMutex(&state->attachLock); goto fail; } /* * Wait until the thread finishes basic initialization. * TODO: cond vars should be waited upon in a loop */ dvmDbgCondWait(&state->threadStartCond, &state->threadStartLock); dvmDbgUnlockMutex(&state->threadStartLock); /* * For suspend=y, wait for the debugger to connect to us or for us to * connect to the debugger. * * The JDWP thread will signal us when it connects successfully or * times out (for timeout=xxx), so we have to check to see what happened * when we wake up. */ if (pParams->suspend) { dvmChangeStatus(NULL, THREAD_VMWAIT); dvmDbgCondWait(&state->attachCond, &state->attachLock); dvmDbgUnlockMutex(&state->attachLock); dvmChangeStatus(NULL, THREAD_RUNNING); if (!dvmJdwpIsActive(state)) { LOGE("JDWP connection failed\n"); goto fail; } LOGI("JDWP connected\n"); /* * Ordinarily we would pause briefly to allow the debugger to set * breakpoints and so on, but for "suspend=y" the VM init code will * pause the VM when it sends the VM_START message. */ } return state; fail: dvmJdwpShutdown(state); // frees state return NULL; }
/* * Implements monitorenter for "synchronized" stuff. * * This does not fail or throw an exception (unless deadlock prediction * is enabled and set to "err" mode). */ void dvmLockObject(Thread* self, Object *obj) { volatile u4 *thinp; ThreadStatus oldStatus; struct timespec tm; long sleepDelayNs; long minSleepDelayNs = 1000000; /* 1 millisecond */ long maxSleepDelayNs = 1000000000; /* 1 second */ u4 thin, newThin, threadId; assert(self != NULL); assert(obj != NULL); threadId = self->threadId; thinp = &obj->lock; retry: thin = *thinp; if (LW_SHAPE(thin) == LW_SHAPE_THIN) { /* * The lock is a thin lock. The owner field is used to * determine the acquire method, ordered by cost. */ if (LW_LOCK_OWNER(thin) == threadId) { /* * The calling thread owns the lock. Increment the * value of the recursion count field. */ obj->lock += 1 << LW_LOCK_COUNT_SHIFT; if (LW_LOCK_COUNT(obj->lock) == LW_LOCK_COUNT_MASK) { /* * The reacquisition limit has been reached. Inflate * the lock so the next acquire will not overflow the * recursion count field. */ inflateMonitor(self, obj); } } else if (LW_LOCK_OWNER(thin) == 0) { /* * The lock is unowned. Install the thread id of the * calling thread into the owner field. This is the * common case. In performance critical code the JIT * will have tried this before calling out to the VM. */ newThin = thin | (threadId << LW_LOCK_OWNER_SHIFT); if (android_atomic_acquire_cas(thin, newThin, (int32_t*)thinp) != 0) { /* * The acquire failed. Try again. */ goto retry; } } else { ALOGV("(%d) spin on lock %p: %#x (%#x) %#x", threadId, &obj->lock, 0, *thinp, thin); /* * The lock is owned by another thread. Notify the VM * that we are about to wait. */ oldStatus = dvmChangeStatus(self, THREAD_MONITOR); /* * Spin until the thin lock is released or inflated. */ sleepDelayNs = 0; for (;;) { thin = *thinp; /* * Check the shape of the lock word. Another thread * may have inflated the lock while we were waiting. */ if (LW_SHAPE(thin) == LW_SHAPE_THIN) { if (LW_LOCK_OWNER(thin) == 0) { /* * The lock has been released. Install the * thread id of the calling thread into the * owner field. */ newThin = thin | (threadId << LW_LOCK_OWNER_SHIFT); if (android_atomic_acquire_cas(thin, newThin, (int32_t *)thinp) == 0) { /* * The acquire succeed. Break out of the * loop and proceed to inflate the lock. */ break; } } else { /* * The lock has not been released. Yield so * the owning thread can run. */ if (sleepDelayNs == 0) { sched_yield(); sleepDelayNs = minSleepDelayNs; } else { tm.tv_sec = 0; tm.tv_nsec = sleepDelayNs; nanosleep(&tm, NULL); /* * Prepare the next delay value. Wrap to * avoid once a second polls for eternity. */ if (sleepDelayNs < maxSleepDelayNs / 2) { sleepDelayNs *= 2; } else { sleepDelayNs = minSleepDelayNs; } } } } else { /* * The thin lock was inflated by another thread. * Let the VM know we are no longer waiting and * try again. */ ALOGV("(%d) lock %p surprise-fattened", threadId, &obj->lock); dvmChangeStatus(self, oldStatus); goto retry; } } ALOGV("(%d) spin on lock done %p: %#x (%#x) %#x", threadId, &obj->lock, 0, *thinp, thin); /* * We have acquired the thin lock. Let the VM know that * we are no longer waiting. */ dvmChangeStatus(self, oldStatus); /* * Fatten the lock. */ inflateMonitor(self, obj); ALOGV("(%d) lock %p fattened", threadId, &obj->lock); } } else { /* * The lock is a fat lock. */ assert(LW_MONITOR(obj->lock) != NULL); lockMonitor(self, LW_MONITOR(obj->lock)); } }
/* * Lock a monitor. */ static void lockMonitor(Thread* self, Monitor* mon) { ThreadStatus oldStatus; u4 waitThreshold, samplePercent; u8 waitStart, waitEnd, waitMs; if (mon->owner == self) { mon->lockCount++; return; } if (dvmTryLockMutex(&mon->lock) != 0) { oldStatus = dvmChangeStatus(self, THREAD_MONITOR); waitThreshold = gDvm.lockProfThreshold; if (waitThreshold) { waitStart = dvmGetRelativeTimeUsec(); } const Method* currentOwnerMethod = mon->ownerMethod; u4 currentOwnerPc = mon->ownerPc; dvmLockMutex(&mon->lock); if (waitThreshold) { waitEnd = dvmGetRelativeTimeUsec(); } dvmChangeStatus(self, oldStatus); if (waitThreshold) { waitMs = (waitEnd - waitStart) / 1000; if (waitMs >= waitThreshold) { samplePercent = 100; } else { samplePercent = 100 * waitMs / waitThreshold; } if (samplePercent != 0 && ((u4)rand() % 100 < samplePercent)) { const char* currentOwnerFileName = "no_method"; u4 currentOwnerLineNumber = 0; if (currentOwnerMethod != NULL) { currentOwnerFileName = dvmGetMethodSourceFile(currentOwnerMethod); if (currentOwnerFileName == NULL) { currentOwnerFileName = "no_method_file"; } currentOwnerLineNumber = dvmLineNumFromPC(currentOwnerMethod, currentOwnerPc); } logContentionEvent(self, waitMs, samplePercent, currentOwnerFileName, currentOwnerLineNumber); } } } mon->owner = self; assert(mon->lockCount == 0); // When debugging, save the current monitor holder for future // acquisition failures to use in sampled logging. if (gDvm.lockProfThreshold > 0) { mon->ownerMethod = NULL; mon->ownerPc = 0; if (self->interpSave.curFrame == NULL) { return; } const StackSaveArea* saveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame); if (saveArea == NULL) { return; } mon->ownerMethod = saveArea->method; mon->ownerPc = (saveArea->xtra.currentPc - saveArea->method->insns); } }