/* * Generate the contents of a THST chunk. The data encompasses all known * threads. * * Response has: * (1b) header len * (1b) bytes per entry * (2b) thread count * Then, for each thread: * (4b) threadId * (1b) thread status * (4b) tid * (4b) utime * (4b) stime * (1b) is daemon? * * The length fields exist in anticipation of adding additional fields * without wanting to break ddms or bump the full protocol version. I don't * think it warrants full versioning. They might be extraneous and could * be removed from a future version. * * Returns a new byte[] with the data inside, or NULL on failure. The * caller must call dvmReleaseTrackedAlloc() on the array. */ ArrayObject* dvmDdmGenerateThreadStats(void) { const int kHeaderLen = 4; const int kBytesPerEntry = 18; dvmLockThreadList(NULL); Thread* thread; int threadCount = 0; for (thread = gDvm.threadList; thread != NULL; thread = thread->next) threadCount++; /* * Create a temporary buffer. We can't perform heap allocation with * the thread list lock held (could cause a GC). The output is small * enough to sit on the stack. */ int bufLen = kHeaderLen + threadCount * kBytesPerEntry; u1 tmpBuf[bufLen]; u1* buf = tmpBuf; set1(buf+0, kHeaderLen); set1(buf+1, kBytesPerEntry); set2BE(buf+2, (u2) threadCount); buf += kHeaderLen; pid_t pid = getpid(); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { unsigned long utime, stime; bool isDaemon; if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) { // failed; drop in empty values utime = stime = 0; } isDaemon = dvmGetFieldBoolean(thread->threadObj, gDvm.offJavaLangThread_daemon); set4BE(buf+0, thread->threadId); set1(buf+4, thread->status); set4BE(buf+5, thread->systemTid); set4BE(buf+9, utime); set4BE(buf+13, stime); set1(buf+17, isDaemon); buf += kBytesPerEntry; } dvmUnlockThreadList(); /* * Create a byte array to hold the data. */ ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT); if (arrayObj != NULL) memcpy(arrayObj->contents, tmpBuf, bufLen); return arrayObj; }
/* * void interrupt() * * Interrupt a thread that is waiting (or is about to wait) on a monitor. */ static void Dalvik_java_lang_VMThread_interrupt(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; Thread* thread; dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); if (thread != NULL) dvmThreadInterrupt(thread); dvmUnlockThreadList(); RETURN_VOID(); }
/* * Visits all threads on the thread list. */ static void visitThreads(RootVisitor *visitor, void *arg) { Thread *thread; assert(visitor != NULL); dvmLockThreadList(dvmThreadSelf()); thread = gDvm.threadList; while (thread) { visitThread(visitor, thread, arg); thread = thread->next; } dvmUnlockThreadList(); }
/* * Find the specified thread and return its stack trace as an array of * StackTraceElement objects. */ ArrayObject* dvmDdmGetStackTraceById(u4 threadId) { Thread* self = dvmThreadSelf(); Thread* thread; int* traceBuf; dvmLockThreadList(self); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { if (thread->threadId == threadId) break; } if (thread == NULL) { LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId); dvmUnlockThreadList(); return NULL; } /* * Suspend the thread, pull out the stack trace, then resume the thread * and release the thread list lock. If we're being asked to examine * our own stack trace, skip the suspend/resume. */ int stackDepth = -1; if (thread != self) dvmSuspendThread(thread); traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth); if (thread != self) dvmResumeThread(thread); dvmUnlockThreadList(); /* * Convert the raw buffer into an array of StackTraceElement. */ ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth); free(traceBuf); return trace; }
/* * void getStatus() * * Gets the Thread status. Result is in VM terms, has to be mapped to * Thread.State by interpreted code. */ static void Dalvik_java_lang_VMThread_getStatus(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; Thread* thread; int result; dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); if (thread != NULL) result = thread->status; else result = THREAD_ZOMBIE; // assume it used to exist and is now gone dvmUnlockThreadList(); RETURN_INT(result); }
/* * void setPriority(int newPriority) * * Alter the priority of the specified thread. "newPriority" will range * from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY (1-10), with "normal" * threads at Thread.NORM_PRIORITY (5). */ static void Dalvik_java_lang_VMThread_setPriority(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; int newPriority = args[1]; Thread* thread; dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); if (thread != NULL) dvmChangeThreadPriority(thread, newPriority); //dvmDumpAllThreads(false); dvmUnlockThreadList(); RETURN_VOID(); }
/* * boolean isInterrupted() * * Determine if the specified thread has been interrupted. Does not clear * the flag. */ static void Dalvik_java_lang_VMThread_isInterrupted(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; Thread* thread; bool interrupted; dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); if (thread != NULL) interrupted = thread->interrupted; else interrupted = false; dvmUnlockThreadList(); RETURN_BOOLEAN(interrupted); }
/* * boolean holdsLock(Object object) * * Returns whether the current thread has a monitor lock on the specific * object. */ static void Dalvik_java_lang_VMThread_holdsLock(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; Object* object = (Object*) args[1]; Thread* thread; if (object == NULL) { dvmThrowNullPointerException("object == null"); RETURN_VOID(); } dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); int result = dvmHoldsLock(thread, object); dvmUnlockThreadList(); RETURN_BOOLEAN(result); }
/* * Turn thread notification on or off. */ void dvmDdmSetThreadNotification(bool enable) { /* * We lock the thread list to avoid sending duplicate events or missing * a thread change. We should be okay holding this lock while sending * the messages out. (We have to hold it while accessing a live thread.) */ dvmLockThreadList(NULL); gDvm.ddmThreadNotification = enable; if (enable) { Thread* thread; for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { //LOGW("notify %d\n", thread->threadId); dvmDdmSendThreadNotification(thread, true); } } dvmUnlockThreadList(); }
/* * void nameChanged(String newName) * * The name of the target thread has changed. We may need to alert DDMS. */ static void Dalvik_java_lang_VMThread_nameChanged(const u4* args, JValue* pResult) { Object* thisPtr = (Object*) args[0]; StringObject* nameStr = (StringObject*) args[1]; Thread* thread; int threadId = -1; /* get the thread's ID */ dvmLockThreadList(NULL); thread = dvmGetThreadFromThreadObject(thisPtr); if (thread != NULL) threadId = thread->threadId; dvmUnlockThreadList(); dvmDdmSendThreadNameChange(threadId, nameStr); //char* str = dvmCreateCstrFromString(nameStr); //ALOGI("UPDATE: threadid=%d now '%s'", threadId, str); //free(str); RETURN_VOID(); }
/* * Dump stack frames, starting from the specified frame and moving down. * * Each frame holds a pointer to the currently executing method, and the * saved program counter from the caller ("previous" frame). This means * we don't have the PC for the current method on the stack, which is * pretty reasonable since it's in the "PC register" for the VM. Because * exceptions need to show the correct line number we actually *do* have * an updated version in the fame's "xtra.currentPc", but it's unreliable. * * Note "framePtr" could be NULL in rare circumstances. */ static void dumpFrames(const DebugOutputTarget* target, void* framePtr, Thread* thread) { const StackSaveArea* saveArea; const Method* method; int checkCount = 0; const u2* currentPc = NULL; bool first = true; /* * We call functions that require us to be holding the thread list lock. * It's probable that the caller has already done so, but it's not * guaranteed. If it's not locked, lock it now. */ bool needThreadUnlock = dvmTryLockThreadList(); /* * The "currentPc" is updated whenever we execute an instruction that * might throw an exception. Show it here. */ if (framePtr != NULL && !dvmIsBreakFrame((u4*)framePtr)) { saveArea = SAVEAREA_FROM_FP(framePtr); if (saveArea->xtra.currentPc != NULL) currentPc = saveArea->xtra.currentPc; } while (framePtr != NULL) { saveArea = SAVEAREA_FROM_FP(framePtr); method = saveArea->method; if (dvmIsBreakFrame((u4*)framePtr)) { //dvmPrintDebugMessage(target, " (break frame)\n"); } else { int relPc; if (currentPc != NULL) relPc = currentPc - saveArea->method->insns; else relPc = -1; std::string methodName(dvmHumanReadableMethod(method, false)); if (dvmIsNativeMethod(method)) { dvmPrintDebugMessage(target, " at %s(Native Method)\n", methodName.c_str()); } else { dvmPrintDebugMessage(target, " at %s(%s:%s%d)\n", methodName.c_str(), dvmGetMethodSourceFile(method), (relPc >= 0 && first) ? "~" : "", relPc < 0 ? -1 : dvmLineNumFromPC(method, relPc)); } if (first) { /* * Decorate WAIT and MONITOR threads with some detail on * the first frame. * * warning: wait status not stable, even in suspend */ if (thread->status == THREAD_WAIT || thread->status == THREAD_TIMED_WAIT) { Monitor* mon = thread->waitMonitor; Object* obj = dvmGetMonitorObject(mon); if (obj != NULL) { Thread* joinThread = NULL; if (obj->clazz == gDvm.classJavaLangVMThread) { joinThread = dvmGetThreadFromThreadObject(obj); } if (joinThread == NULL) { joinThread = dvmGetObjectLockHolder(obj); } printWaitMessage(target, "on", obj, joinThread); } } else if (thread->status == THREAD_MONITOR) { Object* obj; Thread* owner; if (extractMonitorEnterObject(thread, &obj, &owner)) { printWaitMessage(target, "to lock", obj, owner); } } } } /* * Get saved PC for previous frame. There's no savedPc in a "break" * frame, because that represents native or interpreted code * invoked by the VM. The saved PC is sitting in the "PC register", * a local variable on the native stack. */ currentPc = saveArea->savedPc; first = false; if (saveArea->prevFrame != NULL && saveArea->prevFrame <= framePtr) { ALOGW("Warning: loop in stack trace at frame %d (%p -> %p)", checkCount, framePtr, saveArea->prevFrame); break; } framePtr = saveArea->prevFrame; checkCount++; if (checkCount > 300) { dvmPrintDebugMessage(target, " ***** printed %d frames, not showing any more\n", checkCount); break; } } if (needThreadUnlock) { dvmUnlockThreadList(); } }
static void resetCodeCache(void) { Thread* thread; u8 startTime = dvmGetRelativeTimeUsec(); int inJit = 0; int byteUsed = gDvmJit.codeCacheByteUsed; /* If any thread is found stuck in the JIT state, don't reset the cache */ dvmLockThreadList(NULL); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { /* * Crawl the stack to wipe out the returnAddr field so that * 1) the soon-to-be-deleted code in the JIT cache won't be used * 2) or the thread stuck in the JIT land will soon return * to the interpreter land */ crawlDalvikStack(thread, false); if (thread->inJitCodeCache) { inJit++; } /* Cancel any ongoing trace selection */ dvmDisableSubMode(thread, kSubModeJitTraceBuild); } dvmUnlockThreadList(); if (inJit) { ALOGD("JIT code cache reset delayed (%d bytes %d/%d)", gDvmJit.codeCacheByteUsed, gDvmJit.numCodeCacheReset, ++gDvmJit.numCodeCacheResetDelayed); return; } /* Lock the mutex to clean up the work queue */ dvmLockMutex(&gDvmJit.compilerLock); /* Update the translation cache version */ gDvmJit.cacheVersion++; /* Drain the work queue to free the work orders */ while (workQueueLength()) { CompilerWorkOrder work = workDequeue(); free(work.info); } /* Reset the JitEntry table contents to the initial unpopulated state */ dvmJitResetTable(); UNPROTECT_CODE_CACHE(gDvmJit.codeCache, gDvmJit.codeCacheByteUsed); /* * Wipe out the code cache content to force immediate crashes if * stale JIT'ed code is invoked. */ dvmCompilerCacheClear((char *) gDvmJit.codeCache + gDvmJit.templateSize, gDvmJit.codeCacheByteUsed - gDvmJit.templateSize); dvmCompilerCacheFlush((intptr_t) gDvmJit.codeCache, (intptr_t) gDvmJit.codeCache + gDvmJit.codeCacheByteUsed); PROTECT_CODE_CACHE(gDvmJit.codeCache, gDvmJit.codeCacheByteUsed); /* Reset the current mark of used bytes to the end of template code */ gDvmJit.codeCacheByteUsed = gDvmJit.templateSize; gDvmJit.numCompilations = 0; /* Reset the work queue */ memset(gDvmJit.compilerWorkQueue, 0, sizeof(CompilerWorkOrder) * COMPILER_WORK_QUEUE_SIZE); gDvmJit.compilerWorkEnqueueIndex = gDvmJit.compilerWorkDequeueIndex = 0; gDvmJit.compilerQueueLength = 0; /* Reset the IC patch work queue */ dvmLockMutex(&gDvmJit.compilerICPatchLock); gDvmJit.compilerICPatchIndex = 0; dvmUnlockMutex(&gDvmJit.compilerICPatchLock); /* * Reset the inflight compilation address (can only be done in safe points * or by the compiler thread when its thread state is RUNNING). */ gDvmJit.inflightBaseAddr = NULL; /* All clear now */ gDvmJit.codeCacheFull = false; dvmUnlockMutex(&gDvmJit.compilerLock); ALOGD("JIT code cache reset in %lld ms (%d bytes %d/%d)", (dvmGetRelativeTimeUsec() - startTime) / 1000, byteUsed, ++gDvmJit.numCodeCacheReset, gDvmJit.numCodeCacheResetDelayed); }
/* Make sure that the HeapWorker thread hasn't spent an inordinate * amount of time inside a finalizer. * * Aborts the VM if the thread appears to be wedged. * * The caller must hold the heapWorkerLock to guarantee an atomic * read of the watchdog values. */ void dvmAssertHeapWorkerThreadRunning() { if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) { static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime; u8 now = dvmGetRelativeTimeUsec(); u8 delta = now - heapWorkerInterpStartTime; if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT && (gDvm.debuggerActive || gDvm.nativeDebuggerActive)) { /* * Debugger suspension can block the thread indefinitely. For * best results we should reset this explicitly whenever the * HeapWorker thread is resumed. Unfortunately this is also * affected by native debuggers, and we have no visibility * into how they're manipulating us. So, we ignore the * watchdog and just reset the timer. */ LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n"); gDvm.gcHeap->heapWorkerInterpStartTime = now; /* reset timer */ } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) { /* * Before we give up entirely, see if maybe we're just not * getting any CPU time because we're stuck in a background * process group. If we successfully move the thread into the * foreground we'll just leave it there (it doesn't do anything * if the process isn't GCing). */ dvmLockThreadList(NULL); Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle); dvmUnlockThreadList(); if (thread != NULL) { int priChangeFlags, threadPrio; SchedPolicy threadPolicy; priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread, &threadPrio, &threadPolicy); if (priChangeFlags != 0) { LOGI("HeapWorker watchdog expired, raising priority" " and retrying\n"); gDvm.gcHeap->heapWorkerInterpStartTime = now; return; } } char* desc = dexProtoCopyMethodDescriptor( &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n", delta / 1000, gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); free(desc); dvmDumpAllThreads(true); /* try to get a debuggerd dump from the target thread */ dvmNukeThread(thread); /* abort the VM */ dvmAbort(); } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) { char* desc = dexProtoCopyMethodDescriptor( &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n", delta / 1000, gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); free(desc); } } }