diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:14 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:14 -0800 |
| commit | f72d5de56a522ac3be03873bdde26f23a5eeeb3c (patch) | |
| tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /vm/Thread.c | |
| parent | 31e30105703263782efd450d356cd67ea01af3b7 (diff) | |
| download | android_dalvik-f72d5de56a522ac3be03873bdde26f23a5eeeb3c.tar.gz android_dalvik-f72d5de56a522ac3be03873bdde26f23a5eeeb3c.tar.bz2 android_dalvik-f72d5de56a522ac3be03873bdde26f23a5eeeb3c.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'vm/Thread.c')
| -rw-r--r-- | vm/Thread.c | 3266 |
1 files changed, 0 insertions, 3266 deletions
diff --git a/vm/Thread.c b/vm/Thread.c deleted file mode 100644 index 42b527e99..000000000 --- a/vm/Thread.c +++ /dev/null @@ -1,3266 +0,0 @@ -/* - * 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. - */ -/* - * Thread support. - */ -#include "Dalvik.h" - -#include "utils/threads.h" // need Android thread priorities - -#include <stdlib.h> -#include <unistd.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <sys/mman.h> -#include <errno.h> - -#if defined(HAVE_PRCTL) -#include <sys/prctl.h> -#endif - -/* desktop Linux needs a little help with gettid() */ -#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS) -#define __KERNEL__ -# include <linux/unistd.h> -#ifdef _syscall0 -_syscall0(pid_t,gettid) -#else -pid_t gettid() { return syscall(__NR_gettid);} -#endif -#undef __KERNEL__ -#endif - -// change this to LOGV/LOGD to debug thread activity -#define LOG_THREAD LOGVV - -/* -Notes on Threading - -All threads are native pthreads. All threads, except the JDWP debugger -thread, are visible to code running in the VM and to the debugger. (We -don't want the debugger to try to manipulate the thread that listens for -instructions from the debugger.) Internal VM threads are in the "system" -ThreadGroup, all others are in the "main" ThreadGroup, per convention. - -The GC only runs when all threads have been suspended. Threads are -expected to suspend themselves, using a "safe point" mechanism. We check -for a suspend request at certain points in the main interpreter loop, -and on requests coming in from native code (e.g. all JNI functions). -Certain debugger events may inspire threads to self-suspend. - -Native methods must use JNI calls to modify object references to avoid -clashes with the GC. JNI doesn't provide a way for native code to access -arrays of objects as such -- code must always get/set individual entries -- -so it should be possible to fully control access through JNI. - -Internal native VM threads, such as the finalizer thread, must explicitly -check for suspension periodically. In most cases they will be sound -asleep on a condition variable, and won't notice the suspension anyway. - -Threads may be suspended by the GC, debugger, or the SIGQUIT listener -thread. The debugger may suspend or resume individual threads, while the -GC always suspends all threads. Each thread has a "suspend count" that -is incremented on suspend requests and decremented on resume requests. -When the count is zero, the thread is runnable. This allows us to fulfill -a debugger requirement: if the debugger suspends a thread, the thread is -not allowed to run again until the debugger resumes it (or disconnects, -in which case we must resume all debugger-suspended threads). - -Paused threads sleep on a condition variable, and are awoken en masse. -Certain "slow" VM operations, such as starting up a new thread, will be -done in a separate "VMWAIT" state, so that the rest of the VM doesn't -freeze up waiting for the operation to finish. Threads must check for -pending suspension when leaving VMWAIT. - -Because threads suspend themselves while interpreting code or when native -code makes JNI calls, there is no risk of suspending while holding internal -VM locks. All threads can enter a suspended (or native-code-only) state. -Also, we don't have to worry about object references existing solely -in hardware registers. - -We do, however, have to worry about objects that were allocated internally -and aren't yet visible to anything else in the VM. If we allocate an -object, and then go to sleep on a mutex after changing to a non-RUNNING -state (e.g. while trying to allocate a second object), the first object -could be garbage-collected out from under us while we sleep. To manage -this, we automatically add all allocated objects to an internal object -tracking list, and only remove them when we know we won't be suspended -before the object appears in the GC root set. - -The debugger may choose to suspend or resume a single thread, which can -lead to application-level deadlocks; this is expected behavior. The VM -will only check for suspension of single threads when the debugger is -active (the java.lang.Thread calls for this are deprecated and hence are -not supported). Resumption of a single thread is handled by decrementing -the thread's suspend count and sending a broadcast signal to the condition -variable. (This will cause all threads to wake up and immediately go back -to sleep, which isn't tremendously efficient, but neither is having the -debugger attached.) - -The debugger is not allowed to resume threads suspended by the GC. This -is trivially enforced by ignoring debugger requests while the GC is running -(the JDWP thread is suspended during GC). - -The VM maintains a Thread struct for every pthread known to the VM. There -is a java/lang/Thread object associated with every Thread. At present, -there is no safe way to go from a Thread object to a Thread struct except by -locking and scanning the list; this is necessary because the lifetimes of -the two are not closely coupled. We may want to change this behavior, -though at present the only performance impact is on the debugger (see -threadObjToThread()). See also notes about dvmDetachCurrentThread(). -*/ -/* -Alternate implementation (signal-based): - -Threads run without safe points -- zero overhead. The VM uses a signal -(e.g. pthread_kill(SIGUSR1)) to notify threads of suspension or resumption. - -The trouble with using signals to suspend threads is that it means a thread -can be in the middle of an operation when garbage collection starts. -To prevent some sticky situations, we have to introduce critical sections -to the VM code. - -Critical sections temporarily block suspension for a given thread. -The thread must move to a non-blocked state (and self-suspend) after -finishing its current task. If the thread blocks on a resource held -by a suspended thread, we're hosed. - -One approach is to require that no blocking operations, notably -acquisition of mutexes, can be performed within a critical section. -This is too limiting. For example, if thread A gets suspended while -holding the thread list lock, it will prevent the GC or debugger from -being able to safely access the thread list. We need to wrap the critical -section around the entire operation (enter critical, get lock, do stuff, -release lock, exit critical). - -A better approach is to declare that certain resources can only be held -within critical sections. A thread that enters a critical section and -then gets blocked on the thread list lock knows that the thread it is -waiting for is also in a critical section, and will release the lock -before suspending itself. Eventually all threads will complete their -operations and self-suspend. For this to work, the VM must: - - (1) Determine the set of resources that may be accessed from the GC or - debugger threads. The mutexes guarding those go into the "critical - resource set" (CRS). - (2) Ensure that no resource in the CRS can be acquired outside of a - critical section. This can be verified with an assert(). - (3) Ensure that only resources in the CRS can be held while in a critical - section. This is harder to enforce. - -If any of these conditions are not met, deadlock can ensue when grabbing -resources in the GC or debugger (#1) or waiting for threads to suspend -(#2,#3). (You won't actually deadlock in the GC, because if the semantics -above are followed you don't need to lock anything in the GC. The risk is -rather that the GC will access data structures in an intermediate state.) - -This approach requires more care and awareness in the VM than -safe-pointing. Because the GC and debugger are fairly intrusive, there -really aren't any internal VM resources that aren't shared. Thus, the -enter/exit critical calls can be added to internal mutex wrappers, which -makes it easy to get #1 and #2 right. - -An ordering should be established for all locks to avoid deadlocks. - -Monitor locks, which are also implemented with pthread calls, should not -cause any problems here. Threads fighting over such locks will not be in -critical sections and can be suspended freely. - -This can get tricky if we ever need exclusive access to VM and non-VM -resources at the same time. It's not clear if this is a real concern. - -There are (at least) two ways to handle the incoming signals: - - (a) Always accept signals. If we're in a critical section, the signal - handler just returns without doing anything (the "suspend level" - should have been incremented before the signal was sent). Otherwise, - if the "suspend level" is nonzero, we go to sleep. - (b) Block signals in critical sections. This ensures that we can't be - interrupted in a critical section, but requires pthread_sigmask() - calls on entry and exit. - -This is a choice between blocking the message and blocking the messenger. -Because UNIX signals are unreliable (you can only know that you have been -signaled, not whether you were signaled once or 10 times), the choice is -not significant for correctness. The choice depends on the efficiency -of pthread_sigmask() and the desire to actually block signals. Either way, -it is best to ensure that there is only one indication of "blocked"; -having two (i.e. block signals and set a flag, then only send a signal -if the flag isn't set) can lead to race conditions. - -The signal handler must take care to copy registers onto the stack (via -setjmp), so that stack scans find all references. Because we have to scan -native stacks, "exact" GC is not possible with this approach. - -Some other concerns with flinging signals around: - - Odd interactions with some debuggers (e.g. gdb on the Mac) - - Restrictions on some standard library calls during GC (e.g. don't - use printf on stdout to print GC debug messages) -*/ - -#define kMaxThreadId ((1<<15) - 1) -#define kMainThreadId ((1<<1) | 1) - - -static Thread* allocThread(int interpStackSize); -static bool prepareThread(Thread* thread); -static void setThreadSelf(Thread* thread); -static void unlinkThread(Thread* thread); -static void freeThread(Thread* thread); -static void assignThreadId(Thread* thread); -static bool createFakeEntryFrame(Thread* thread); -static bool createFakeRunFrame(Thread* thread); -static void* interpThreadStart(void* arg); -static void* internalThreadStart(void* arg); -static void threadExitUncaughtException(Thread* thread, Object* group); -static void threadExitCheck(void* arg); -static void waitForThreadSuspend(Thread* self, Thread* thread); -static int getThreadPriorityFromSystem(void); - - -/* - * Initialize thread list and main thread's environment. We need to set - * up some basic stuff so that dvmThreadSelf() will work when we start - * loading classes (e.g. to check for exceptions). - */ -bool dvmThreadStartup(void) -{ - Thread* thread; - - /* allocate a TLS slot */ - if (pthread_key_create(&gDvm.pthreadKeySelf, threadExitCheck) != 0) { - LOGE("ERROR: pthread_key_create failed\n"); - return false; - } - - /* test our pthread lib */ - if (pthread_getspecific(gDvm.pthreadKeySelf) != NULL) - LOGW("WARNING: newly-created pthread TLS slot is not NULL\n"); - - /* prep thread-related locks and conditions */ - dvmInitMutex(&gDvm.threadListLock); - pthread_cond_init(&gDvm.threadStartCond, NULL); - //dvmInitMutex(&gDvm.vmExitLock); - pthread_cond_init(&gDvm.vmExitCond, NULL); - dvmInitMutex(&gDvm._threadSuspendLock); - dvmInitMutex(&gDvm.threadSuspendCountLock); - pthread_cond_init(&gDvm.threadSuspendCountCond, NULL); -#ifdef WITH_DEADLOCK_PREDICTION - dvmInitMutex(&gDvm.deadlockHistoryLock); -#endif - - /* - * Dedicated monitor for Thread.sleep(). - * TODO: change this to an Object* so we don't have to expose this - * call, and we interact better with JDWP monitor calls. Requires - * deferring the object creation to much later (e.g. final "main" - * thread prep) or until first use. - */ - gDvm.threadSleepMon = dvmCreateMonitor(NULL); - - gDvm.threadIdMap = dvmAllocBitVector(kMaxThreadId, false); - - thread = allocThread(gDvm.stackSize); - if (thread == NULL) - return false; - - /* switch mode for when we run initializers */ - thread->status = THREAD_RUNNING; - - /* - * We need to assign the threadId early so we can lock/notify - * object monitors. We'll set the "threadObj" field later. - */ - prepareThread(thread); - gDvm.threadList = thread; - -#ifdef COUNT_PRECISE_METHODS - gDvm.preciseMethods = dvmPointerSetAlloc(200); -#endif - - return true; -} - -/* - * We're a little farther up now, and can load some basic classes. - * - * We're far enough along that we can poke at java.lang.Thread and friends, - * but should not assume that static initializers have run (or cause them - * to do so). That means no object allocations yet. - */ -bool dvmThreadObjStartup(void) -{ - /* - * Cache the locations of these classes. It's likely that we're the - * first to reference them, so they're being loaded now. - */ - gDvm.classJavaLangThread = - dvmFindSystemClassNoInit("Ljava/lang/Thread;"); - gDvm.classJavaLangVMThread = - dvmFindSystemClassNoInit("Ljava/lang/VMThread;"); - gDvm.classJavaLangThreadGroup = - dvmFindSystemClassNoInit("Ljava/lang/ThreadGroup;"); - if (gDvm.classJavaLangThread == NULL || - gDvm.classJavaLangThreadGroup == NULL || - gDvm.classJavaLangThreadGroup == NULL) - { - LOGE("Could not find one or more essential thread classes\n"); - return false; - } - - /* - * Cache field offsets. This makes things a little faster, at the - * expense of hard-coding non-public field names into the VM. - */ - gDvm.offJavaLangThread_vmThread = - dvmFindFieldOffset(gDvm.classJavaLangThread, - "vmThread", "Ljava/lang/VMThread;"); - gDvm.offJavaLangThread_group = - dvmFindFieldOffset(gDvm.classJavaLangThread, - "group", "Ljava/lang/ThreadGroup;"); - gDvm.offJavaLangThread_daemon = - dvmFindFieldOffset(gDvm.classJavaLangThread, "daemon", "Z"); - gDvm.offJavaLangThread_name = - dvmFindFieldOffset(gDvm.classJavaLangThread, - "name", "Ljava/lang/String;"); - gDvm.offJavaLangThread_priority = - dvmFindFieldOffset(gDvm.classJavaLangThread, "priority", "I"); - - if (gDvm.offJavaLangThread_vmThread < 0 || - gDvm.offJavaLangThread_group < 0 || - gDvm.offJavaLangThread_daemon < 0 || - gDvm.offJavaLangThread_name < 0 || - gDvm.offJavaLangThread_priority < 0) - { - LOGE("Unable to find all fields in java.lang.Thread\n"); - return false; - } - - gDvm.offJavaLangVMThread_thread = - dvmFindFieldOffset(gDvm.classJavaLangVMThread, - "thread", "Ljava/lang/Thread;"); - gDvm.offJavaLangVMThread_vmData = - dvmFindFieldOffset(gDvm.classJavaLangVMThread, "vmData", "I"); - if (gDvm.offJavaLangVMThread_thread < 0 || - gDvm.offJavaLangVMThread_vmData < 0) - { - LOGE("Unable to find all fields in java.lang.VMThread\n"); - return false; - } - - /* - * Cache the vtable offset for "run()". - * - * We don't want to keep the Method* because then we won't find see - * methods defined in subclasses. - */ - Method* meth; - meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThread, "run", "()V"); - if (meth == NULL) { - LOGE("Unable to find run() in java.lang.Thread\n"); - return false; - } - gDvm.voffJavaLangThread_run = meth->methodIndex; - - /* - * Cache vtable offsets for ThreadGroup methods. - */ - meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThreadGroup, - "removeThread", "(Ljava/lang/Thread;)V"); - if (meth == NULL) { - LOGE("Unable to find removeThread(Thread) in java.lang.ThreadGroup\n"); - return false; - } - gDvm.voffJavaLangThreadGroup_removeThread = meth->methodIndex; - - return true; -} - -/* - * All threads should be stopped by now. Clean up some thread globals. - */ -void dvmThreadShutdown(void) -{ - if (gDvm.threadList != NULL) { - assert(gDvm.threadList->next == NULL); - assert(gDvm.threadList->prev == NULL); - freeThread(gDvm.threadList); - gDvm.threadList = NULL; - } - - dvmFreeBitVector(gDvm.threadIdMap); - - dvmFreeMonitorList(); - - pthread_key_delete(gDvm.pthreadKeySelf); -} - - -/* - * Grab the suspend count global lock. - */ -static inline void lockThreadSuspendCount(void) -{ - /* - * Don't try to change to VMWAIT here. When we change back to RUNNING - * we have to check for a pending suspend, which results in grabbing - * this lock recursively. Doesn't work with "fast" pthread mutexes. - * - * This lock is always held for very brief periods, so as long as - * mutex ordering is respected we shouldn't stall. - */ - int cc = pthread_mutex_lock(&gDvm.threadSuspendCountLock); - assert(cc == 0); -} - -/* - * Release the suspend count global lock. - */ -static inline void unlockThreadSuspendCount(void) -{ - dvmUnlockMutex(&gDvm.threadSuspendCountLock); -} - -/* - * Grab the thread list global lock. - * - * This is held while "suspend all" is trying to make everybody stop. If - * the shutdown is in progress, and somebody tries to grab the lock, they'll - * have to wait for the GC to finish. Therefore it's important that the - * thread not be in RUNNING mode. - * - * We don't have to check to see if we should be suspended once we have - * the lock. Nobody can suspend all threads without holding the thread list - * lock while they do it, so by definition there isn't a GC in progress. - */ -void dvmLockThreadList(Thread* self) -{ - ThreadStatus oldStatus; - - if (self == NULL) /* try to get it from TLS */ - self = dvmThreadSelf(); - - if (self != NULL) { - oldStatus = self->status; - self->status = THREAD_VMWAIT; - } else { - /* happens for JNI AttachCurrentThread [not anymore?] */ - //LOGW("NULL self in dvmLockThreadList\n"); - oldStatus = -1; // shut up gcc - } - - int cc = pthread_mutex_lock(&gDvm.threadListLock); - assert(cc == 0); - - if (self != NULL) - self->status = oldStatus; -} - -/* - * Release the thread list global lock. - */ -void dvmUnlockThreadList(void) -{ - int cc = pthread_mutex_unlock(&gDvm.threadListLock); - assert(cc == 0); -} - - -/* - * Grab the "thread suspend" lock. This is required to prevent the - * GC and the debugger from simultaneously suspending all threads. - * - * If we fail to get the lock, somebody else is trying to suspend all - * threads -- including us. If we go to sleep on the lock we'll deadlock - * the VM. Loop until we get it or somebody puts us to sleep. - */ -static void lockThreadSuspend(const char* who, SuspendCause why) -{ - const int kMaxRetries = 10; - const int kSpinSleepTime = 3*1000*1000; /* 3s */ - u8 startWhen = 0; // init req'd to placate gcc - int sleepIter = 0; - int cc; - - do { - cc = pthread_mutex_trylock(&gDvm._threadSuspendLock); - if (cc != 0) { - if (!dvmCheckSuspendPending(NULL)) { - /* - * Could be unusual JNI-attach thing, could be we hit - * the window as the suspend or resume was started. Could - * also be the debugger telling us to resume at roughly - * the same time we're posting an event. - */ - LOGI("threadid=%d ODD: thread-suspend lock held (%s:%d)" - " but suspend not pending\n", - dvmThreadSelf()->threadId, who, why); - } - - /* give the lock-holder a chance to do some work */ - if (sleepIter == 0) - startWhen = dvmGetRelativeTimeUsec(); - if (!dvmIterativeSleep(sleepIter++, kSpinSleepTime, startWhen)) { - LOGE("threadid=%d: couldn't get thread-suspend lock (%s:%d)," - " bailing\n", - dvmThreadSelf()->threadId, who, why); - dvmDumpAllThreads(false); - dvmAbort(); - } - } - } while (cc != 0); - assert(cc == 0); -} - -/* - * Release the "thread suspend" lock. - */ -static inline void unlockThreadSuspend(void) -{ - int cc = pthread_mutex_unlock(&gDvm._threadSuspendLock); - assert(cc == 0); -} - - -/* - * Kill any daemon threads that still exist. All of ours should be - * stopped, so these should be Thread objects or JNI-attached threads - * started by the application. Actively-running threads are likely - * to crash the process if they continue to execute while the VM - * shuts down, so we really need to kill or suspend them. (If we want - * the VM to restart within this process, we need to kill them, but that - * leaves open the possibility of orphaned resources.) - * - * Waiting for the thread to suspend may be unwise at this point, but - * if one of these is wedged in a critical section then we probably - * would've locked up on the last GC attempt. - * - * It's possible for this function to get called after a failed - * initialization, so be careful with assumptions about the environment. - */ -void dvmSlayDaemons(void) -{ - Thread* self = dvmThreadSelf(); - Thread* target; - Thread* nextTarget; - - if (self == NULL) - return; - - //dvmEnterCritical(self); - dvmLockThreadList(self); - - target = gDvm.threadList; - while (target != NULL) { - if (target == self) { - target = target->next; - continue; - } - - if (!dvmGetFieldBoolean(target->threadObj, - gDvm.offJavaLangThread_daemon)) - { - LOGW("threadid=%d: non-daemon id=%d still running at shutdown?!\n", - self->threadId, target->threadId); - target = target->next; - continue; - } - - LOGI("threadid=%d: killing leftover daemon threadid=%d [TODO]\n", - self->threadId, target->threadId); - // TODO: suspend and/or kill the thread - // (at the very least, we can "rescind their JNI privileges") - - /* remove from list */ - nextTarget = target->next; - unlinkThread(target); - - freeThread(target); - target = nextTarget; - } - - dvmUnlockThreadList(); - //dvmExitCritical(self); -} - - -/* - * Finish preparing the parts of the Thread struct required to support - * JNI registration. - */ -bool dvmPrepMainForJni(JNIEnv* pEnv) -{ - Thread* self; - - /* main thread is always first in list at this point */ - self = gDvm.threadList; - assert(self->threadId == kMainThreadId); - - /* create a "fake" JNI frame at the top of the main thread interp stack */ - if (!createFakeEntryFrame(self)) - return false; - - /* fill these in, since they weren't ready at dvmCreateJNIEnv time */ - dvmSetJniEnvThreadId(pEnv, self); - dvmSetThreadJNIEnv(self, (JNIEnv*) pEnv); - - return true; -} - - -/* - * Finish preparing the main thread, allocating some objects to represent - * it. As part of doing so, we finish initializing Thread and ThreadGroup. - */ -bool dvmPrepMainThread(void) -{ - Thread* thread; - Object* groupObj; - Object* threadObj; - Object* vmThreadObj; - StringObject* threadNameStr; - Method* init; - JValue unused; - - LOGV("+++ finishing prep on main VM thread\n"); - - /* main thread is always first in list at this point */ - thread = gDvm.threadList; - assert(thread->threadId == kMainThreadId); - - /* - * Make sure the classes are initialized. We have to do this before - * we create an instance of them. - */ - if (!dvmInitClass(gDvm.classJavaLangClass)) { - LOGE("'Class' class failed to initialize\n"); - return false; - } - if (!dvmInitClass(gDvm.classJavaLangThreadGroup) || - !dvmInitClass(gDvm.classJavaLangThread) || - !dvmInitClass(gDvm.classJavaLangVMThread)) - { - LOGE("thread classes failed to initialize\n"); - return false; - } - - groupObj = dvmGetMainThreadGroup(); - if (groupObj == NULL) - return false; - - /* - * Allocate and construct a Thread with the internal-creation - * constructor. - */ - threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_DEFAULT); - if (threadObj == NULL) { - LOGE("unable to allocate main thread object\n"); - return false; - } - dvmReleaseTrackedAlloc(threadObj, NULL); - - threadNameStr = dvmCreateStringFromCstr("main", ALLOC_DEFAULT); - if (threadNameStr == NULL) - return false; - dvmReleaseTrackedAlloc((Object*)threadNameStr, NULL); - - init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>", - "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V"); - assert(init != NULL); - dvmCallMethod(thread, init, threadObj, &unused, groupObj, threadNameStr, - THREAD_NORM_PRIORITY, false); - if (dvmCheckException(thread)) { - LOGE("exception thrown while constructing main thread object\n"); - return false; - } - - /* - * Allocate and construct a VMThread. - */ - vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT); - if (vmThreadObj == NULL) { - LOGE("unable to allocate main vmthread object\n"); - return false; - } - dvmReleaseTrackedAlloc(vmThreadObj, NULL); - - init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangVMThread, "<init>", - "(Ljava/lang/Thread;)V"); - dvmCallMethod(thread, init, vmThreadObj, &unused, threadObj); - if (dvmCheckException(thread)) { - LOGE("exception thrown while constructing main vmthread object\n"); - return false; - } - - /* set the VMThread.vmData field to our Thread struct */ - assert(gDvm.offJavaLangVMThread_vmData != 0); - dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)thread); - - /* - * Stuff the VMThread back into the Thread. From this point on, other - * Threads will see that this Thread is running. - */ - dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, - vmThreadObj); - - thread->threadObj = threadObj; - - /* - * Finish our thread prep. - */ - - /* include self in non-daemon threads (mainly for AttachCurrentThread) */ - gDvm.nonDaemonThreadCount++; - - return true; -} - - -/* - * Alloc and initialize a Thread struct. - * - * "threadObj" is the java.lang.Thread object. It will be NULL for the - * main VM thread, but non-NULL for everything else. - * - * Does not create any objects, just stuff on the system (malloc) heap. (If - * this changes, we need to use ALLOC_NO_GC. And also verify that we're - * ready to load classes at the time this is called.) - */ -static Thread* allocThread(int interpStackSize) -{ - Thread* thread; - u1* stackBottom; - - thread = (Thread*) calloc(1, sizeof(Thread)); - if (thread == NULL) - return NULL; - - assert(interpStackSize >= kMinStackSize && interpStackSize <=kMaxStackSize); - - thread->status = THREAD_INITIALIZING; - thread->suspendCount = 0; - -#ifdef WITH_ALLOC_LIMITS - thread->allocLimit = -1; -#endif - - /* - * Allocate and initialize the interpreted code stack. We essentially - * "lose" the alloc pointer, which points at the bottom of the stack, - * but we can get it back later because we know how big the stack is. - * - * The stack must be aligned on a 4-byte boundary. - */ -#ifdef MALLOC_INTERP_STACK - stackBottom = (u1*) malloc(interpStackSize); - if (stackBottom == NULL) { - free(thread); - return NULL; - } - memset(stackBottom, 0xc5, interpStackSize); // stop valgrind complaints -#else - stackBottom = mmap(NULL, interpStackSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); - if (stackBottom == MAP_FAILED) { - free(thread); - return NULL; - } -#endif - - assert(((u4)stackBottom & 0x03) == 0); // looks like our malloc ensures this - thread->interpStackSize = interpStackSize; - thread->interpStackStart = stackBottom + interpStackSize; - thread->interpStackEnd = stackBottom + STACK_OVERFLOW_RESERVE; - - /* give the thread code a chance to set things up */ - dvmInitInterpStack(thread, interpStackSize); - - return thread; -} - -/* - * Get a meaningful thread ID. At present this only has meaning under Linux, - * where getpid() and gettid() sometimes agree and sometimes don't depending - * on your thread model (try "export LD_ASSUME_KERNEL=2.4.19"). - */ -pid_t dvmGetSysThreadId(void) -{ -#ifdef HAVE_GETTID - return gettid(); -#else - return getpid(); -#endif -} - -/* - * Finish initialization of a Thread struct. - * - * This must be called while executing in the new thread, but before the - * thread is added to the thread list. - * - * *** NOTE: The threadListLock must be held by the caller (needed for - * assignThreadId()). - */ -static bool prepareThread(Thread* thread) -{ - assignThreadId(thread); - thread->handle = pthread_self(); - thread->systemTid = dvmGetSysThreadId(); - - //LOGI("SYSTEM TID IS %d (pid is %d)\n", (int) thread->systemTid, - // (int) getpid()); - setThreadSelf(thread); - - LOGV("threadid=%d: interp stack at %p\n", - thread->threadId, thread->interpStackStart - thread->interpStackSize); - - /* - * Initialize invokeReq. - */ - pthread_mutex_init(&thread->invokeReq.lock, NULL); - pthread_cond_init(&thread->invokeReq.cv, NULL); - - /* - * Initialize our reference tracking tables. - * - * The JNI local ref table *must* be fixed-size because we keep pointers - * into the table in our stack frames. - * - * Most threads won't use jniMonitorRefTable, so we clear out the - * structure but don't call the init function (which allocs storage). - */ - if (!dvmInitReferenceTable(&thread->jniLocalRefTable, - kJniLocalRefMax, kJniLocalRefMax)) - return false; - if (!dvmInitReferenceTable(&thread->internalLocalRefTable, - kInternalRefDefault, kInternalRefMax)) - return false; - - memset(&thread->jniMonitorRefTable, 0, sizeof(thread->jniMonitorRefTable)); - - return true; -} - -/* - * Remove a thread from the internal list. - * Clear out the links to make it obvious that the thread is - * no longer on the list. Caller must hold gDvm.threadListLock. - */ -static void unlinkThread(Thread* thread) -{ - LOG_THREAD("threadid=%d: removing from list\n", thread->threadId); - if (thread == gDvm.threadList) { - assert(thread->prev == NULL); - gDvm.threadList = thread->next; - } else { - assert(thread->prev != NULL); - thread->prev->next = thread->next; - } - if (thread->next != NULL) - thread->next->prev = thread->prev; - thread->prev = thread->next = NULL; -} - -/* - * Free a Thread struct, and all the stuff allocated within. - */ -static void freeThread(Thread* thread) -{ - if (thread == NULL) - return; - - /* thread->threadId is zero at this point */ - LOGVV("threadid=%d: freeing\n", thread->threadId); - - if (thread->interpStackStart != NULL) { - u1* interpStackBottom; - - interpStackBottom = thread->interpStackStart; - interpStackBottom -= thread->interpStackSize; -#ifdef MALLOC_INTERP_STACK - free(interpStackBottom); -#else - if (munmap(interpStackBottom, thread->interpStackSize) != 0) - LOGW("munmap(thread stack) failed\n"); -#endif - } - - dvmClearReferenceTable(&thread->jniLocalRefTable); - dvmClearReferenceTable(&thread->internalLocalRefTable); - if (&thread->jniMonitorRefTable.table != NULL) - dvmClearReferenceTable(&thread->jniMonitorRefTable); - - free(thread); -} - -/* - * Like pthread_self(), but on a Thread*. - */ -Thread* dvmThreadSelf(void) -{ - return (Thread*) pthread_getspecific(gDvm.pthreadKeySelf); -} - -/* - * Explore our sense of self. Stuffs the thread pointer into TLS. - */ -static void setThreadSelf(Thread* thread) -{ - int cc; - - cc = pthread_setspecific(gDvm.pthreadKeySelf, thread); - if (cc != 0) { - /* - * Sometimes this fails under Bionic with EINVAL during shutdown. - * This can happen if the timing is just right, e.g. a thread - * fails to attach during shutdown, but the "fail" path calls - * here to ensure we clean up after ourselves. - */ - if (thread != NULL) { - LOGE("pthread_setspecific(%p) failed, err=%d\n", thread, cc); - dvmAbort(); /* the world is fundamentally hosed */ - } - } -} - -/* - * This is associated with the pthreadKeySelf key. It's called by the - * pthread library when a thread is exiting and the "self" pointer in TLS - * is non-NULL, meaning the VM hasn't had a chance to clean up. In normal - * operation this should never be called. - * - * This is mainly of use to ensure that we don't leak resources if, for - * example, a thread attaches itself to us with AttachCurrentThread and - * then exits without notifying the VM. - */ -static void threadExitCheck(void* arg) -{ - Thread* thread = (Thread*) arg; - - LOGI("In threadExitCheck %p\n", arg); - assert(thread != NULL); - - if (thread->status != THREAD_ZOMBIE) { - /* TODO: instead of failing, we could call dvmDetachCurrentThread() */ - LOGE("Native thread exited without telling us\n"); - dvmAbort(); - } -} - - -/* - * Assign the threadId. This needs to be a small integer so that our - * "thin" locks fit in a small number of bits. - * - * We reserve zero for use as an invalid ID. - * - * This must be called with threadListLock held (unless we're still - * initializing the system). - */ -static void assignThreadId(Thread* thread) -{ - /* Find a small unique integer. threadIdMap is a vector of - * kMaxThreadId bits; dvmAllocBit() returns the index of a - * bit, meaning that it will always be < kMaxThreadId. - * - * The thin locking magic requires that the low bit is always - * set, so we do it once, here. - */ - int num = dvmAllocBit(gDvm.threadIdMap); - if (num < 0) { - LOGE("Ran out of thread IDs\n"); - dvmAbort(); // TODO: make this a non-fatal error result - } - - thread->threadId = ((num + 1) << 1) | 1; - - assert(thread->threadId != 0); - assert(thread->threadId != DVM_LOCK_INITIAL_THIN_VALUE); -} - -/* - * Give back the thread ID. - */ -static void releaseThreadId(Thread* thread) -{ - assert(thread->threadId > 0); - dvmClearBit(gDvm.threadIdMap, (thread->threadId >> 1) - 1); - thread->threadId = 0; -} - - -/* - * Add a stack frame that makes it look like the native code in the main - * thread was originally invoked from interpreted code. This gives us a - * place to hang JNI local references. The VM spec says (v2 5.2) that the - * VM begins by executing "main" in a class, so in a way this brings us - * closer to the spec. - */ -static bool createFakeEntryFrame(Thread* thread) -{ - assert(thread->threadId == kMainThreadId); // main thread only - - /* find the method on first use */ - if (gDvm.methFakeNativeEntry == NULL) { - ClassObject* nativeStart; - Method* mainMeth; - - nativeStart = dvmFindSystemClassNoInit( - "Ldalvik/system/NativeStart;"); - if (nativeStart == NULL) { - LOGE("Unable to find dalvik.system.NativeStart class\n"); - return false; - } - - /* - * Because we are creating a frame that represents application code, we - * want to stuff the application class loader into the method's class - * loader field, even though we're using the system class loader to - * load it. This makes life easier over in JNI FindClass (though it - * could bite us in other ways). - * - * Unfortunately this is occurring too early in the initialization, - * of necessity coming before JNI is initialized, and we're not quite - * ready to set up the application class loader. - * - * So we save a pointer to the method in gDvm.methFakeNativeEntry - * and check it in FindClass. The method is private so nobody else - * can call it. - */ - //nativeStart->classLoader = dvmGetSystemClassLoader(); - - mainMeth = dvmFindDirectMethodByDescriptor(nativeStart, - "main", "([Ljava/lang/String;)V"); - if (mainMeth == NULL) { - LOGE("Unable to find 'main' in dalvik.system.NativeStart\n"); - return false; - } - - gDvm.methFakeNativeEntry = mainMeth; - } - - return dvmPushJNIFrame(thread, gDvm.methFakeNativeEntry); -} - - -/* - * Add a stack frame that makes it look like the native thread has been - * executing interpreted code. This gives us a place to hang JNI local - * references. - */ -static bool createFakeRunFrame(Thread* thread) -{ - ClassObject* nativeStart; - Method* runMeth; - - assert(thread->threadId != 1); // not for main thread - - nativeStart = - dvmFindSystemClassNoInit("Ldalvik/system/NativeStart;"); - if (nativeStart == NULL) { - LOGE("Unable to find dalvik.system.NativeStart class\n"); - return false; - } - - runMeth = dvmFindVirtualMethodByDescriptor(nativeStart, "run", "()V"); - if (runMeth == NULL) { - LOGE("Unable to find 'run' in dalvik.system.NativeStart\n"); - return false; - } - - return dvmPushJNIFrame(thread, runMeth); -} - -/* - * Helper function to set the name of the current thread - */ -static void setThreadName(const char *threadName) -{ -#if defined(HAVE_PRCTL) - int hasAt = 0; - int hasDot = 0; - const char *s = threadName; - while (*s) { - if (*s == '.') hasDot = 1; - else if (*s == '@') hasAt = 1; - s++; - } - int len = s - threadName; - if (len < 15 || hasAt || !hasDot) { - s = threadName; - } else { - s = threadName + len - 15; - } - prctl(PR_SET_NAME, (unsigned long) s, 0, 0, 0); -#endif -} - -/* - * Create a thread as a result of java.lang.Thread.start(). - * - * We do have to worry about some concurrency problems, e.g. programs - * that try to call Thread.start() on the same object from multiple threads. - * (This will fail for all but one, but we have to make sure that it succeeds - * for exactly one.) - * - * Some of the complexity here arises from our desire to mimic the - * Thread vs. VMThread class decomposition we inherited. We've been given - * a Thread, and now we need to create a VMThread and then populate both - * objects. We also need to create one of our internal Thread objects. - * - * Pass in a stack size of 0 to get the default. - */ -bool dvmCreateInterpThread(Object* threadObj, int reqStackSize) -{ - pthread_attr_t threadAttr; - pthread_t threadHandle; - Thread* self; - Thread* newThread = NULL; - Object* vmThreadObj = NULL; - int stackSize; - - assert(threadObj != NULL); - - if(gDvm.zygote) { - dvmThrowException("Ljava/lang/IllegalStateException;", - "No new threads in -Xzygote mode"); - - goto fail; - } - - self = dvmThreadSelf(); - if (reqStackSize == 0) - stackSize = gDvm.stackSize; - else if (reqStackSize < kMinStackSize) - stackSize = kMinStackSize; - else if (reqStackSize > kMaxStackSize) - stackSize = kMaxStackSize; - else - stackSize = reqStackSize; - - pthread_attr_init(&threadAttr); - pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); - - /* - * To minimize the time spent in the critical section, we allocate the - * vmThread object here. - */ - vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT); - if (vmThreadObj == NULL) - goto fail; - - newThread = allocThread(stackSize); - if (newThread == NULL) - goto fail; - newThread->threadObj = threadObj; - - assert(newThread->status == THREAD_INITIALIZING); - - /* - * We need to lock out other threads while we test and set the - * "vmThread" field in java.lang.Thread, because we use that to determine - * if this thread has been started before. We use the thread list lock - * because it's handy and we're going to need to grab it again soon - * anyway. - */ - dvmLockThreadList(self); - - if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) { - dvmUnlockThreadList(); - dvmThrowException("Ljava/lang/IllegalThreadStateException;", - "thread has already been started"); - goto fail; - } - - /* - * There are actually three data structures: Thread (object), VMThread - * (object), and Thread (C struct). All of them point to at least one - * other. - * - * As soon as "VMThread.vmData" is assigned, other threads can start - * making calls into us (e.g. setPriority). - */ - dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread); - dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj); - - /* - * Thread creation might take a while, so release the lock. - */ - dvmUnlockThreadList(); - - if (pthread_create(&threadHandle, &threadAttr, interpThreadStart, - newThread) != 0) - { - /* - * Failure generally indicates that we have exceeded system - * resource limits. VirtualMachineError is probably too severe, - * so use OutOfMemoryError. - */ - LOGE("Thread creation failed (err=%s)\n", strerror(errno)); - - dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, NULL); - - dvmThrowException("Ljava/lang/OutOfMemoryError;", - "thread creation failed"); - goto fail; - } - - /* - * We need to wait for the thread to start. Otherwise, depending on - * the whims of the OS scheduler, we could return and the code in our - * thread could try to do operations on the new thread before it had - * finished starting. - * - * The new thread will lock the thread list, change its state to - * THREAD_STARTING, broadcast to gDvm.threadStartCond, and then sleep - * on gDvm.threadStartCond (which uses the thread list lock). This - * thread (the parent) will either see that the thread is already ready - * after we grab the thread list lock, or will be awakened from the - * condition variable on the broadcast. - * - * We don't want to stall the rest of the VM while the new thread - * starts, which can happen if the GC wakes up at the wrong moment. - * So, we change our own status to VMWAIT, and self-suspend if - * necessary after we finish adding the new thread. - * - * - * We have to deal with an odd race with the GC/debugger suspension - * mechanism when creating a new thread. The information about whether - * or not a thread should be suspended is contained entirely within - * the Thread struct; this is usually cleaner to deal with than having - * one or more globally-visible suspension flags. The trouble is that - * we could create the thread while the VM is trying to suspend all - * threads. The suspend-count won't be nonzero for the new thread, - * so dvmChangeStatus(THREAD_RUNNING) won't cause a suspension. - * - * The easiest way to deal with this is to prevent the new thread from - * running until the parent says it's okay. This results in the - * following sequence of events for a "badly timed" GC: - * - * - call pthread_create() - * - lock thread list - * - put self into THREAD_VMWAIT so GC doesn't wait for us - * - sleep on condition var (mutex = thread list lock) until child starts - * + GC triggered by another thread - * + thread list locked; suspend counts updated; thread list unlocked - * + loop waiting for all runnable threads to suspend - * + success, start GC - * o child thread wakes, signals condition var to wake parent - * o child waits for parent ack on condition variable - * - we wake up, locking thread list - * - add child to thread list - * - unlock thread list - * - change our state back to THREAD_RUNNING; GC causes us to suspend - * + GC finishes; all threads in thread list are resumed - * - lock thread list - * - set child to THREAD_VMWAIT, and signal it to start - * - unlock thread list - * o child resumes - * o child changes state to THREAD_RUNNING - * - * The above shows the GC starting up during thread creation, but if - * it starts anywhere after VMThread.create() is called it will - * produce the same series of events. - * - * Once the child is in the thread list, it will be suspended and - * resumed like any other thread. In the above scenario the resume-all - * code will try to resume the new thread, which was never actually - * suspended, and try to decrement the child's thread suspend count to -1. - * We can catch this in the resume-all code. - * - * Bouncing back and forth between threads like this adds a small amount - * of scheduler overhead to thread startup. - * - * One alternative to having the child wait for the parent would be - * to have the child inherit the parents' suspension count. This - * would work for a GC, since we can safely assume that the parent - * thread didn't cause it, but we must only do so if the parent suspension - * was caused by a suspend-all. If the parent was being asked to - * suspend singly by the debugger, the child should not inherit the value. - * - * We could also have a global "new thread suspend count" that gets - * picked up by new threads before changing state to THREAD_RUNNING. - * This would be protected by the thread list lock and set by a - * suspend-all. - */ - dvmLockThreadList(self); - assert(self->status == THREAD_RUNNING); - self->status = THREAD_VMWAIT; - while (newThread->status != THREAD_STARTING) - pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock); - - LOG_THREAD("threadid=%d: adding to list\n", newThread->threadId); - newThread->next = gDvm.threadList->next; - if (newThread->next != NULL) - newThread->next->prev = newThread; - newThread->prev = gDvm.threadList; - gDvm.threadList->next = newThread; - - if (!dvmGetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon)) - gDvm.nonDaemonThreadCount++; // guarded by thread list lock - - dvmUnlockThreadList(); - - /* change status back to RUNNING, self-suspending if necessary */ - dvmChangeStatus(self, THREAD_RUNNING); - - /* - * Tell the new thread to start. - * - * We must hold the thread list lock before messing with another thread. - * In the general case we would also need to verify that newThread was - * still in the thread list, but in our case the thread has not started - * executing user code and therefore has not had a chance to exit. - * - * We move it to VMWAIT, and it then shifts itself to RUNNING, which - * comes with a suspend-pending check. - */ - dvmLockThreadList(self); - - assert(newThread->status == THREAD_STARTING); - newThread->status = THREAD_VMWAIT; - pthread_cond_broadcast(&gDvm.threadStartCond); - - dvmUnlockThreadList(); - - dvmReleaseTrackedAlloc(vmThreadObj, NULL); - return true; - -fail: - freeThread(newThread); - dvmReleaseTrackedAlloc(vmThreadObj, NULL); - return false; -} - -/* - * pthread entry function for threads started from interpreted code. - */ -static void* interpThreadStart(void* arg) -{ - Thread* self = (Thread*) arg; - - char *threadName = dvmGetThreadName(self); - setThreadName(threadName); - free(threadName); - - /* - * Finish initializing the Thread struct. - */ - prepareThread(self); - - LOG_THREAD("threadid=%d: created from interp\n", self->threadId); - - /* - * Change our status and wake our parent, who will add us to the - * thread list and advance our state to VMWAIT. - */ - dvmLockThreadList(self); - self->status = THREAD_STARTING; - pthread_cond_broadcast(&gDvm.threadStartCond); - - /* - * Wait until the parent says we can go. Assuming there wasn't a - * suspend pending, this will happen immediately. When it completes, - * we're full-fledged citizens of the VM. - * - * We have to use THREAD_VMWAIT here rather than THREAD_RUNNING - * because the pthread_cond_wait below needs to reacquire a lock that - * suspend-all is also interested in. If we get unlucky, the parent could - * change us to THREAD_RUNNING, then a GC could start before we get - * signaled, and suspend-all will grab the thread list lock and then - * wait for us to suspend. We'll be in the tail end of pthread_cond_wait - * trying to get the lock. - */ - while (self->status != THREAD_VMWAIT) - pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock); - - dvmUnlockThreadList(); - - /* - * Add a JNI context. - */ - self->jniEnv = dvmCreateJNIEnv(self); - - /* - * Change our state so the GC will wait for us from now on. If a GC is - * in progress this call will suspend us. - */ - dvmChangeStatus(self, THREAD_RUNNING); - - /* - * Notify the debugger & DDM. The debugger notification may cause - * us to suspend ourselves (and others). - */ - if (gDvm.debuggerConnected) - dvmDbgPostThreadStart(self); - - /* - * Set the system thread priority according to the Thread object's - * priority level. We don't usually need to do this, because both the - * Thread object and system thread priorities inherit from parents. The - * tricky case is when somebody creates a Thread object, calls - * setPriority(), and then starts the thread. We could manage this with - * a "needs priority update" flag to avoid the redundant call. - */ - int priority = dvmGetFieldBoolean(self->threadObj, - gDvm.offJavaLangThread_priority); - dvmChangeThreadPriority(self, priority); - - /* - * Execute the "run" method. - * - * At this point our stack is empty, so somebody who comes looking for - * stack traces right now won't have much to look at. This is normal. - */ - Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run]; - JValue unused; - - LOGV("threadid=%d: calling run()\n", self->threadId); - assert(strcmp(run->name, "run") == 0); - dvmCallMethod(self, run, self->threadObj, &unused); - LOGV("threadid=%d: exiting\n", self->threadId); - - /* - * Remove the thread from various lists, report its death, and free - * its resources. - */ - dvmDetachCurrentThread(); - - return NULL; -} - -/* - * The current thread is exiting with an uncaught exception. The - * Java programming language allows the application to provide a - * thread-exit-uncaught-exception handler for the VM, for a specific - * Thread, and for all threads in a ThreadGroup. - * - * Version 1.5 added the per-thread handler. We need to call - * "uncaughtException" in the handler object, which is either the - * ThreadGroup object or the Thread-specific handler. - */ -static void threadExitUncaughtException(Thread* self, Object* group) -{ - Object* exception; - Object* handlerObj; - ClassObject* throwable; - Method* uncaughtHandler = NULL; - InstField* threadHandler; - - LOGW("threadid=%d: thread exiting with uncaught exception (group=%p)\n", - self->threadId, group); - assert(group != NULL); - - /* - * Get a pointer to the exception, then clear out the one in the - * thread. We don't want to have it set when executing interpreted code. - */ - exception = dvmGetException(self); - dvmAddTrackedAlloc(exception, self); - dvmClearException(self); - - /* - * Get the Thread's "uncaughtHandler" object. Use it if non-NULL; - * else use "group" (which is an instance of UncaughtExceptionHandler). - */ - threadHandler = dvmFindInstanceField(gDvm.classJavaLangThread, - "uncaughtHandler", "Ljava/lang/Thread$UncaughtExceptionHandler;"); - if (threadHandler == NULL) { - LOGW("WARNING: no 'uncaughtHandler' field in java/lang/Thread\n"); - goto bail; - } - handlerObj = dvmGetFieldObject(self->threadObj, threadHandler->byteOffset); - if (handlerObj == NULL) - handlerObj = group; - - /* - * Find the "uncaughtHandler" field in this object. - */ - uncaughtHandler = dvmFindVirtualMethodHierByDescriptor(handlerObj->clazz, - "uncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V"); - - if (uncaughtHandler != NULL) { - //LOGI("+++ calling %s.uncaughtException\n", - // handlerObj->clazz->descriptor); - JValue unused; - dvmCallMethod(self, uncaughtHandler, handlerObj, &unused, - self->threadObj, exception); - } else { - /* restore it and dump a stack trace */ - LOGW("WARNING: no 'uncaughtException' method in class %s\n", - handlerObj->clazz->descriptor); - dvmSetException(self, exception); - dvmLogExceptionStackTrace(); - } - -bail: - dvmReleaseTrackedAlloc(exception, self); -} - - -/* - * Create an internal VM thread, for things like JDWP and finalizers. - * - * The easiest way to do this is create a new thread and then use the - * JNI AttachCurrentThread implementation. - * - * This does not return until after the new thread has begun executing. - */ -bool dvmCreateInternalThread(pthread_t* pHandle, const char* name, - InternalThreadStart func, void* funcArg) -{ - InternalStartArgs* pArgs; - Object* systemGroup; - pthread_attr_t threadAttr; - volatile Thread* newThread = NULL; - volatile int createStatus = 0; - - systemGroup = dvmGetSystemThreadGroup(); - if (systemGroup == NULL) - return false; - - pArgs = (InternalStartArgs*) malloc(sizeof(*pArgs)); - pArgs->func = func; - pArgs->funcArg = funcArg; - pArgs->name = strdup(name); // storage will be owned by new thread - pArgs->group = systemGroup; - pArgs->isDaemon = true; - pArgs->pThread = &newThread; - pArgs->pCreateStatus = &createStatus; - - pthread_attr_init(&threadAttr); - //pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); - - if (pthread_create(pHandle, &threadAttr, internalThreadStart, - pArgs) != 0) - { - LOGE("internal thread creation failed\n"); - free(pArgs->name); - free(pArgs); - return false; - } - - /* - * Wait for the child to start. This gives us an opportunity to make - * sure that the thread started correctly, and allows our caller to - * assume that the thread has started running. - * - * Because we aren't holding a lock across the thread creation, it's - * possible that the child will already have completed its - * initialization. Because the child only adjusts "createStatus" while - * holding the thread list lock, the initial condition on the "while" - * loop will correctly avoid the wait if this occurs. - * - * It's also possible that we'll have to wait for the thread to finish - * being created, and as part of allocating a Thread object it might - * need to initiate a GC. We switch to VMWAIT while we pause. - */ - Thread* self = dvmThreadSelf(); - int oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); - dvmLockThreadList(self); - while (createStatus == 0) - pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock); - - if (newThread == NULL) { - LOGW("internal thread create failed (createStatus=%d)\n", createStatus); - assert(createStatus < 0); - /* don't free pArgs -- if pthread_create succeeded, child owns it */ - dvmUnlockThreadList(); - dvmChangeStatus(self, oldStatus); - return false; - } - - /* thread could be in any state now (except early init states) */ - //assert(newThread->status == THREAD_RUNNING); - - dvmUnlockThreadList(); - dvmChangeStatus(self, oldStatus); - - return true; -} - -/* - * pthread entry function for internally-created threads. - * - * We are expected to free "arg" and its contents. If we're a daemon - * thread, and we get cancelled abruptly when the VM shuts down, the - * storage won't be freed. If this becomes a concern we can make a copy - * on the stack. - */ -static void* internalThreadStart(void* arg) -{ - InternalStartArgs* pArgs = (InternalStartArgs*) arg; - JavaVMAttachArgs jniArgs; - - jniArgs.version = JNI_VERSION_1_2; - jniArgs.name = pArgs->name; - jniArgs.group = pArgs->group; - - setThreadName(pArgs->name); - - /* use local jniArgs as stack top */ - if (dvmAttachCurrentThread(&jniArgs, pArgs->isDaemon)) { - /* - * Tell the parent of our success. - * - * threadListLock is the mutex for threadStartCond. - */ - dvmLockThreadList(dvmThreadSelf()); - *pArgs->pCreateStatus = 1; - *pArgs->pThread = dvmThreadSelf(); - pthread_cond_broadcast(&gDvm.threadStartCond); - dvmUnlockThreadList(); - - LOG_THREAD("threadid=%d: internal '%s'\n", - dvmThreadSelf()->threadId, pArgs->name); - - /* execute */ - (*pArgs->func)(pArgs->funcArg); - - /* detach ourselves */ - dvmDetachCurrentThread(); - } else { - /* - * Tell the parent of our failure. We don't have a Thread struct, - * so we can't be suspended, so we don't need to enter a critical - * section. - */ - dvmLockThreadList(dvmThreadSelf()); - *pArgs->pCreateStatus = -1; - assert(*pArgs->pThread == NULL); - pthread_cond_broadcast(&gDvm.threadStartCond); - dvmUnlockThreadList(); - - assert(*pArgs->pThread == NULL); - } - - free(pArgs->name); - free(pArgs); - return NULL; -} - -/* - * Attach the current thread to the VM. - * - * Used for internally-created threads and JNI's AttachCurrentThread. - */ -bool dvmAttachCurrentThread(const JavaVMAttachArgs* pArgs, bool isDaemon) -{ - Thread* self = NULL; - Object* threadObj = NULL; - Object* vmThreadObj = NULL; - StringObject* threadNameStr = NULL; - Method* init; - bool ok, ret; - - /* establish a basic sense of self */ - self = allocThread(gDvm.stackSize); - if (self == NULL) - goto fail; - setThreadSelf(self); - - /* - * Create Thread and VMThread objects. We have to use ALLOC_NO_GC - * because this thread is not yet visible to the VM. We could also - * just grab the GC lock earlier, but that leaves us executing - * interpreted code with the lock held, which is not prudent. - * - * The alloc calls will block if a GC is in progress, so we don't need - * to check for global suspension here. - * - * It's also possible for the allocation calls to *cause* a GC. - */ - //BUG: deadlock if a GC happens here during HeapWorker creation - threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_NO_GC); - if (threadObj == NULL) - goto fail; - vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_NO_GC); - if (vmThreadObj == NULL) - goto fail; - - self->threadObj = threadObj; - dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)self); - - /* - * Do some java.lang.Thread constructor prep before we lock stuff down. - */ - if (pArgs->name != NULL) { - threadNameStr = dvmCreateStringFromCstr(pArgs->name, ALLOC_NO_GC); - if (threadNameStr == NULL) { - assert(dvmCheckException(dvmThreadSelf())); - goto fail; - } - } - - init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>", - "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V"); - if (init == NULL) { - assert(dvmCheckException(dvmThreadSelf())); - goto fail; - } - - /* - * Finish our thread prep. We need to do this before invoking any - * interpreted code. prepareThread() requires that we hold the thread - * list lock. - */ - dvmLockThreadList(self); - ok = prepareThread(self); - dvmUnlockThreadList(); - if (!ok) - goto fail; - - self->jniEnv = dvmCreateJNIEnv(self); - if (self->jniEnv == NULL) - goto fail; - - /* - * Create a "fake" JNI frame at the top of the main thread interp stack. - * It isn't really necessary for the internal threads, but it gives - * the debugger something to show. It is essential for the JNI-attached - * threads. - */ - if (!createFakeRunFrame(self)) - goto fail; - - /* - * The native side of the thread is ready; add it to the list. - */ - LOG_THREAD("threadid=%d: adding to list (attached)\n", self->threadId); - - /* Start off in VMWAIT, because we may be about to block - * on the heap lock, and we don't want any suspensions - * to wait for us. - */ - self->status = THREAD_VMWAIT; - - /* - * Add ourselves to the thread list. Once we finish here we are - * visible to the debugger and the GC. - */ - dvmLockThreadList(self); - - self->next = gDvm.threadList->next; - if (self->next != NULL) - self->next->prev = self; - self->prev = gDvm.threadList; - gDvm.threadList->next = self; - if (!isDaemon) - gDvm.nonDaemonThreadCount++; - - dvmUnlockThreadList(); - - /* - * It's possible that a GC is currently running. Our thread - * wasn't in the list when the GC started, so it's not properly - * suspended in that case. Synchronize on the heap lock (held - * when a GC is happening) to guarantee that any GCs from here - * on will see this thread in the list. - */ - dvmLockMutex(&gDvm.gcHeapLock); - dvmUnlockMutex(&gDvm.gcHeapLock); - - /* - * Switch to the running state now that we're ready for - * suspensions. This call may suspend. - */ - dvmChangeStatus(self, THREAD_RUNNING); - - /* - * Now we're ready to run some interpreted code. - * - * We need to construct the Thread object and set the VMThread field. - * Setting VMThread tells interpreted code that we're alive. - * - * Call the (group, name, priority, daemon) constructor on the Thread. - * This sets the thread's name and adds it to the specified group, and - * provides values for priority and daemon (which are normally inherited - * from the current thread). - */ - JValue unused; - dvmCallMethod(self, init, threadObj, &unused, (Object*)pArgs->group, - threadNameStr, getThreadPriorityFromSystem(), isDaemon); - if (dvmCheckException(self)) { - LOGE("exception thrown while constructing attached thread object\n"); - goto fail_unlink; - } - //if (isDaemon) - // dvmSetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon, true); - - /* - * Set the VMThread field, which tells interpreted code that we're alive. - * - * The risk of a thread start collision here is very low; somebody - * would have to be deliberately polling the ThreadGroup list and - * trying to start threads against anything it sees, which would - * generally cause problems for all thread creation. However, for - * correctness we test "vmThread" before setting it. - */ - if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) { - dvmThrowException("Ljava/lang/IllegalThreadStateException;", - "thread has already been started"); - /* We don't want to free anything associated with the thread - * because someone is obviously interested in it. Just let - * it go and hope it will clean itself up when its finished. - * This case should never happen anyway. - * - * Since we're letting it live, we need to finish setting it up. - * We just have to let the caller know that the intended operation - * has failed. - * - * [ This seems strange -- stepping on the vmThread object that's - * already present seems like a bad idea. TODO: figure this out. ] - */ - ret = false; - } else - ret = true; - dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj); - - /* These are now reachable from the thread groups. */ - dvmClearAllocFlags(threadObj, ALLOC_NO_GC); - dvmClearAllocFlags(vmThreadObj, ALLOC_NO_GC); - - /* - * The thread is ready to go; let the debugger see it. - */ - self->threadObj = threadObj; - - LOG_THREAD("threadid=%d: attached from native, name=%s\n", - self->threadId, pArgs->name); - - /* tell the debugger & DDM */ - if (gDvm.debuggerConnected) - dvmDbgPostThreadStart(self); - - return ret; - -fail_unlink: - dvmLockThreadList(self); - unlinkThread(self); - if (!isDaemon) - gDvm.nonDaemonThreadCount--; - dvmUnlockThreadList(); - /* fall through to "fail" */ -fail: - dvmClearAllocFlags(threadObj, ALLOC_NO_GC); - dvmClearAllocFlags(vmThreadObj, ALLOC_NO_GC); - if (self != NULL) { - if (self->jniEnv != NULL) { - dvmDestroyJNIEnv(self->jniEnv); - self->jniEnv = NULL; - } - freeThread(self); - } - setThreadSelf(NULL); - return false; -} - -/* - * Detach the thread from the various data structures, notify other threads - * that are waiting to "join" it, and free up all heap-allocated storage. - * - * Used for all threads. - * - * When we get here the interpreted stack should be empty. The JNI 1.6 spec - * requires us to enforce this for the DetachCurrentThread call, probably - * because it also says that DetachCurrentThread causes all monitors - * associated with the thread to be released. (Because the stack is empty, - * we only have to worry about explicit JNI calls to MonitorEnter.) - * - * THOUGHT: - * We might want to avoid freeing our internal Thread structure until the - * associated Thread/VMThread objects get GCed. Our Thread is impossible to - * get to once the thread shuts down, but there is a small possibility of - * an operation starting in another thread before this thread halts, and - * finishing much later (perhaps the thread got stalled by a weird OS bug). - * We don't want something like Thread.isInterrupted() crawling through - * freed storage. Can do with a Thread finalizer, or by creating a - * dedicated ThreadObject class for java/lang/Thread and moving all of our - * state into that. - */ -void dvmDetachCurrentThread(void) -{ - Thread* self = dvmThreadSelf(); - Object* vmThread; - Object* group; - - /* - * Make sure we're not detaching a thread that's still running. (This - * could happen with an explicit JNI detach call.) - * - * A thread created by interpreted code will finish with a depth of - * zero, while a JNI-attached thread will have the synthetic "stack - * starter" native method at the top. - */ - int curDepth = dvmComputeExactFrameDepth(self->curFrame); - if (curDepth != 0) { - bool topIsNative = false; - - if (curDepth == 1) { - /* not expecting a lingering break frame; just look at curFrame */ - assert(!dvmIsBreakFrame(self->curFrame)); - StackSaveArea* ssa = SAVEAREA_FROM_FP(self->curFrame); - if (dvmIsNativeMethod(ssa->method)) - topIsNative = true; - } - - if (!topIsNative) { - LOGE("ERROR: detaching thread with interp frames (count=%d)\n", - curDepth); - dvmDumpThread(self, false); - dvmAbort(); - } - } - - group = dvmGetFieldObject(self->threadObj, gDvm.offJavaLangThread_group); - LOG_THREAD("threadid=%d: detach (group=%p)\n", self->threadId, group); - - /* - * Release any held monitors. Since there are no interpreted stack - * frames, the only thing left are the monitors held by JNI MonitorEnter - * calls. - */ - dvmReleaseJniMonitors(self); - - /* - * Do some thread-exit uncaught exception processing if necessary. - */ - if (dvmCheckException(self)) - threadExitUncaughtException(self, group); - - /* - * Remove the thread from the thread group. - */ - if (group != NULL) { - Method* removeThread = - group->clazz->vtable[gDvm.voffJavaLangThreadGroup_removeThread]; - JValue unused; - dvmCallMethod(self, removeThread, group, &unused, self->threadObj); - } - - /* - * Clear the vmThread reference in the Thread object. Interpreted code - * will now see that this Thread is not running. As this may be the - * only reference to the VMThread object that the VM knows about, we - * have to create an internal reference to it first. - */ - vmThread = dvmGetFieldObject(self->threadObj, - gDvm.offJavaLangThread_vmThread); - dvmAddTrackedAlloc(vmThread, self); - dvmSetFieldObject(self->threadObj, gDvm.offJavaLangThread_vmThread, NULL); - - /* clear out our struct Thread pointer, since it's going away */ - dvmSetFieldObject(vmThread, gDvm.offJavaLangVMThread_vmData, NULL); - - /* - * Tell the debugger & DDM. This may cause the current thread or all - * threads to suspend. - * - * The JDWP spec is somewhat vague about when this happens, other than - * that it's issued by the dying thread, which may still appear in - * an "all threads" listing. - */ - if (gDvm.debuggerConnected) - dvmDbgPostThreadDeath(self); - - /* - * Thread.join() is implemented as an Object.wait() on the VMThread - * object. Signal anyone who is waiting. - */ - dvmLockObject(self, vmThread); - dvmObjectNotifyAll(self, vmThread); - dvmUnlockObject(self, vmThread); - - dvmReleaseTrackedAlloc(vmThread, self); - vmThread = NULL; - - /* - * We're done manipulating objects, so it's okay if the GC runs in - * parallel with us from here out. It's important to do this if - * profiling is enabled, since we can wait indefinitely. - */ - self->status = THREAD_VMWAIT; - -#ifdef WITH_PROFILER - /* - * If we're doing method trace profiling, we don't want threads to exit, - * because if they do we'll end up reusing thread IDs. This complicates - * analysis and makes it impossible to have reasonable output in the - * "threads" section of the "key" file. - * - * We need to do this after Thread.join() completes, or other threads - * could get wedged. Since self->threadObj is still valid, the Thread - * object will not get GCed even though we're no longer in the ThreadGroup - * list (which is important since the profiling thread needs to get - * the thread's name). - */ - MethodTraceState* traceState = &gDvm.methodTrace; - - dvmLockMutex(&traceState->startStopLock); - if (traceState->traceEnabled) { - LOGI("threadid=%d: waiting for method trace to finish\n", - self->threadId); - while (traceState->traceEnabled) { - int cc; - cc = pthread_cond_wait(&traceState->threadExitCond, - &traceState->startStopLock); - assert(cc == 0); - } - } - dvmUnlockMutex(&traceState->startStopLock); -#endif - - dvmLockThreadList(self); - - /* - * Lose the JNI context. - */ - dvmDestroyJNIEnv(self->jniEnv); - self->jniEnv = NULL; - - self->status = THREAD_ZOMBIE; - - /* - * Remove ourselves from the internal thread list. - */ - unlinkThread(self); - - /* - * If we're the last one standing, signal anybody waiting in - * DestroyJavaVM that it's okay to exit. - */ - if (!dvmGetFieldBoolean(self->threadObj, gDvm.offJavaLangThread_daemon)) { - gDvm.nonDaemonThreadCount--; // guarded by thread list lock - - if (gDvm.nonDaemonThreadCount == 0) { - int cc; - - LOGV("threadid=%d: last non-daemon thread\n", self->threadId); - //dvmDumpAllThreads(false); - // cond var guarded by threadListLock, which we already hold - cc = pthread_cond_signal(&gDvm.vmExitCond); - assert(cc == 0); - } - } - - LOGV("threadid=%d: bye!\n", self->threadId); - releaseThreadId(self); - dvmUnlockThreadList(); - - setThreadSelf(NULL); - freeThread(self); -} - - -/* - * Suspend a single thread. Do not use to suspend yourself. - * - * This is used primarily for debugger/DDMS activity. Does not return - * until the thread has suspended or is in a "safe" state (e.g. executing - * native code outside the VM). - * - * The thread list lock should be held before calling here -- it's not - * entirely safe to hang on to a Thread* from another thread otherwise. - * (We'd need to grab it here anyway to avoid clashing with a suspend-all.) - */ -void dvmSuspendThread(Thread* thread) -{ - assert(thread != NULL); - assert(thread != dvmThreadSelf()); - //assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState)); - - lockThreadSuspendCount(); - thread->suspendCount++; - thread->dbgSuspendCount++; - - LOG_THREAD("threadid=%d: suspend++, now=%d\n", - thread->threadId, thread->suspendCount); - unlockThreadSuspendCount(); - - waitForThreadSuspend(dvmThreadSelf(), thread); -} - -/* - * Reduce the suspend count of a thread. If it hits zero, tell it to - * resume. - * - * Used primarily for debugger/DDMS activity. The thread in question - * might have been suspended singly or as part of a suspend-all operation. - * - * The thread list lock should be held before calling here -- it's not - * entirely safe to hang on to a Thread* from another thread otherwise. - * (We'd need to grab it here anyway to avoid clashing with a suspend-all.) - */ -void dvmResumeThread(Thread* thread) -{ - assert(thread != NULL); - assert(thread != dvmThreadSelf()); - //assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState)); - - lockThreadSuspendCount(); - if (thread->suspendCount > 0) { - thread->suspendCount--; - thread->dbgSuspendCount--; - } else { - LOG_THREAD("threadid=%d: suspendCount already zero\n", - thread->threadId); - } - - LOG_THREAD("threadid=%d: suspend--, now=%d\n", - thread->threadId, thread->suspendCount); - - if (thread->suspendCount == 0) { - int cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond); - assert(cc == 0); - } - - unlockThreadSuspendCount(); -} - -/* - * Suspend yourself, as a result of debugger activity. - */ -void dvmSuspendSelf(bool jdwpActivity) -{ - Thread* self = dvmThreadSelf(); - - /* debugger thread may not suspend itself due to debugger activity! */ - assert(gDvm.jdwpState != NULL); - if (self->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) { - assert(false); - return; - } - - /* - * Collisions with other suspends aren't really interesting. We want - * to ensure that we're the only one fiddling with the suspend count - * though. - */ - lockThreadSuspendCount(); - self->suspendCount++; - self->dbgSuspendCount++; - - /* - * Suspend ourselves. - */ - assert(self->suspendCount > 0); - self->isSuspended = true; - LOG_THREAD("threadid=%d: self-suspending (dbg)\n", self->threadId); - - /* - * Tell JDWP that we've completed suspension. The JDWP thread can't - * tell us to resume before we're fully asleep because we hold the - * suspend count lock. - * - * If we got here via waitForDebugger(), don't do this part. - */ - if (jdwpActivity) { - //LOGI("threadid=%d: clearing wait-for-event (my handle=%08x)\n", - // self->threadId, (int) self->handle); - dvmJdwpClearWaitForEventThread(gDvm.jdwpState); - } - - while (self->suspendCount != 0) { - int cc; - cc = pthread_cond_wait(&gDvm.threadSuspendCountCond, - &gDvm.threadSuspendCountLock); - assert(cc == 0); - if (self->suspendCount != 0) { - LOGD("threadid=%d: still suspended after undo (s=%d d=%d)\n", - self->threadId, self->suspendCount, self->dbgSuspendCount); - } - } - assert(self->suspendCount == 0 && self->dbgSuspendCount == 0); - self->isSuspended = false; - LOG_THREAD("threadid=%d: self-reviving (dbg), status=%d\n", - self->threadId, self->status); - - unlockThreadSuspendCount(); -} - - -#ifdef HAVE_GLIBC -# define NUM_FRAMES 20 -# include <execinfo.h> -/* - * glibc-only stack dump function. Requires link with "--export-dynamic". - * - * TODO: move this into libs/cutils and make it work for all platforms. - */ -static void printBackTrace(void) -{ - void* array[NUM_FRAMES]; - size_t size; - char** strings; - size_t i; - - size = backtrace(array, NUM_FRAMES); - strings = backtrace_symbols(array, size); - - LOGW("Obtained %zd stack frames.\n", size); - - for (i = 0; i < size; i++) - LOGW("%s\n", strings[i]); - - free(strings); -} -#else -static void printBackTrace(void) {} -#endif - -/* - * Dump the state of the current thread and that of another thread that - * we think is wedged. - */ -static void dumpWedgedThread(Thread* thread) -{ - char exePath[1024]; - - /* - * The "executablepath" function in libutils is host-side only. - */ - strcpy(exePath, "-"); -#ifdef HAVE_GLIBC - { - char proc[100]; - sprintf(proc, "/proc/%d/exe", getpid()); - int len; - - len = readlink(proc, exePath, sizeof(exePath)-1); - exePath[len] = '\0'; - } -#endif - - LOGW("dumping state: process %s %d\n", exePath, getpid()); - dvmDumpThread(dvmThreadSelf(), false); - printBackTrace(); - - // dumping a running thread is risky, but could be useful - dvmDumpThread(thread, true); - - - // stop now and get a core dump - //abort(); -} - - -/* - * Wait for another thread to see the pending suspension and stop running. - * It can either suspend itself or go into a non-running state such as - * VMWAIT or NATIVE in which it cannot interact with the GC. - * - * If we're running at a higher priority, sched_yield() may not do anything, - * so we need to sleep for "long enough" to guarantee that the other - * thread has a chance to finish what it's doing. Sleeping for too short - * a period (e.g. less than the resolution of the sleep clock) might cause - * the scheduler to return immediately, so we want to start with a - * "reasonable" value and expand. - * - * This does not return until the other thread has stopped running. - * Eventually we time out and the VM aborts. - * - * This does not try to detect the situation where two threads are - * waiting for each other to suspend. In normal use this is part of a - * suspend-all, which implies that the suspend-all lock is held, or as - * part of a debugger action in which the JDWP thread is always the one - * doing the suspending. (We may need to re-evaluate this now that - * getThreadStackTrace is implemented as suspend-snapshot-resume.) - * - * TODO: track basic stats about time required to suspend VM. - */ -static void waitForThreadSuspend(Thread* self, Thread* thread) -{ - const int kMaxRetries = 10; - const int kSpinSleepTime = 750*1000; /* 0.75s */ - - int sleepIter = 0; - int retryCount = 0; - u8 startWhen = 0; // init req'd to placate gcc - - while (thread->status == THREAD_RUNNING && !thread->isSuspended) { - if (sleepIter == 0) // get current time on first iteration - startWhen = dvmGetRelativeTimeUsec(); - - if (!dvmIterativeSleep(sleepIter++, kSpinSleepTime, startWhen)) { - LOGW("threadid=%d (h=%d): spin on suspend threadid=%d (handle=%d)\n", - self->threadId, (int)self->handle, - thread->threadId, (int)thread->handle); - dumpWedgedThread(thread); - - // keep going; could be slow due to valgrind - sleepIter = 0; - - if (retryCount++ == kMaxRetries) { - LOGE("threadid=%d: stuck on threadid=%d, giving up\n", - self->threadId, thread->threadId); - dvmDumpAllThreads(false); - dvmAbort(); - } - } - } -} - -/* - * Suspend all threads except the current one. This is used by the GC, - * the debugger, and by any thread that hits a "suspend all threads" - * debugger event (e.g. breakpoint or exception). - * - * If thread N hits a "suspend all threads" breakpoint, we don't want it - * to suspend the JDWP thread. For the GC, we do, because the debugger can - * create objects and even execute arbitrary code. The "why" argument - * allows the caller to say why the suspension is taking place. - * - * This can be called when a global suspend has already happened, due to - * various debugger gymnastics, so keeping an "everybody is suspended" flag - * doesn't work. - * - * DO NOT grab any locks before calling here. We grab & release the thread - * lock and suspend lock here (and we're not using recursive threads), and - * we might have to self-suspend if somebody else beats us here. - * - * The current thread may not be attached to the VM. This can happen if - * we happen to GC as the result of an allocation of a Thread object. - */ -void dvmSuspendAllThreads(SuspendCause why) -{ - Thread* self = dvmThreadSelf(); - Thread* thread; - - assert(why != 0); - - /* - * Start by grabbing the thread suspend lock. If we can't get it, most - * likely somebody else is in the process of performing a suspend or - * resume, so lockThreadSuspend() will cause us to self-suspend. - * - * We keep the lock until all other threads are suspended. - */ - lockThreadSuspend("susp-all", why); - - LOG_THREAD("threadid=%d: SuspendAll starting\n", self->threadId); - - /* - * This is possible if the current thread was in VMWAIT mode when a - * suspend-all happened, and then decided to do its own suspend-all. - * This can happen when a couple of threads have simultaneous events - * of interest to the debugger. - */ - //assert(self->suspendCount == 0); - - /* - * Increment everybody's suspend count (except our own). - */ - dvmLockThreadList(self); - - lockThreadSuspendCount(); - for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { - if (thread == self) - continue; - - /* debugger events don't suspend JDWP thread */ - if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) && - thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) - continue; - - thread->suspendCount++; - if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) - thread->dbgSuspendCount++; - } - unlockThreadSuspendCount(); - - /* - * Wait for everybody in THREAD_RUNNING state to stop. Other states - * indicate the code is either running natively or sleeping quietly. - * Any attempt to transition back to THREAD_RUNNING will cause a check - * for suspension, so it should be impossible for anything to execute - * interpreted code or modify objects (assuming native code plays nicely). - * - * It's also okay if the thread transitions to a non-RUNNING state. - * - * Note we released the threadSuspendCountLock before getting here, - * so if another thread is fiddling with its suspend count (perhaps - * self-suspending for the debugger) it won't block while we're waiting - * in here. - */ - for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { - if (thread == self) - continue; - - /* debugger events don't suspend JDWP thread */ - if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) && - thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) - continue; - - /* wait for the other thread to see the pending suspend */ - waitForThreadSuspend(self, thread); - - LOG_THREAD("threadid=%d: threadid=%d status=%d c=%d dc=%d isSusp=%d\n", - self->threadId, - thread->threadId, thread->status, thread->suspendCount, - thread->dbgSuspendCount, thread->isSuspended); - } - - dvmUnlockThreadList(); - unlockThreadSuspend(); - - LOG_THREAD("threadid=%d: SuspendAll complete\n", self->threadId); -} - -/* - * Resume all threads that are currently suspended. - * - * The "why" must match with the previous suspend. - */ -void dvmResumeAllThreads(SuspendCause why) -{ - Thread* self = dvmThreadSelf(); - Thread* thread; - int cc; - - lockThreadSuspend("res-all", why); /* one suspend/resume at a time */ - LOG_THREAD("threadid=%d: ResumeAll starting\n", self->threadId); - - /* - * Decrement the suspend counts for all threads. No need for atomic - * writes, since nobody should be moving until we decrement the count. - * We do need to hold the thread list because of JNI attaches. - */ - dvmLockThreadList(self); - lockThreadSuspendCount(); - for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { - if (thread == self) - continue; - - /* debugger events don't suspend JDWP thread */ - if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) && - thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) - continue; - - if (thread->suspendCount > 0) { - thread->suspendCount--; - if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) - thread->dbgSuspendCount--; - } else { - LOG_THREAD("threadid=%d: suspendCount already zero\n", - thread->threadId); - } - } - unlockThreadSuspendCount(); - dvmUnlockThreadList(); - - /* - * Broadcast a notification to all suspended threads, some or all of - * which may choose to wake up. No need to wait for them. - */ - lockThreadSuspendCount(); - cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond); - assert(cc == 0); - unlockThreadSuspendCount(); - - unlockThreadSuspend(); - - LOG_THREAD("threadid=%d: ResumeAll complete\n", self->threadId); -} - -/* - * Undo any debugger suspensions. This is called when the debugger - * disconnects. - */ -void dvmUndoDebuggerSuspensions(void) -{ - Thread* self = dvmThreadSelf(); - Thread* thread; - int cc; - - lockThreadSuspend("undo", SUSPEND_FOR_DEBUG); - LOG_THREAD("threadid=%d: UndoDebuggerSusp starting\n", self->threadId); - - /* - * Decrement the suspend counts for all threads. No need for atomic - * writes, since nobody should be moving until we decrement the count. - * We do need to hold the thread list because of JNI attaches. - */ - dvmLockThreadList(self); - lockThreadSuspendCount(); - for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { - if (thread == self) - continue; - - /* debugger events don't suspend JDWP thread */ - if (thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) { - assert(thread->dbgSuspendCount == 0); - continue; - } - - assert(thread->suspendCount >= thread->dbgSuspendCount); - thread->suspendCount -= thread->dbgSuspendCount; - thread->dbgSuspendCount = 0; - } - unlockThreadSuspendCount(); - dvmUnlockThreadList(); - - /* - * Broadcast a notification to all suspended threads, some or all of - * which may choose to wake up. No need to wait for them. - */ - lockThreadSuspendCount(); - cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond); - assert(cc == 0); - unlockThreadSuspendCount(); - - unlockThreadSuspend(); - - LOG_THREAD("threadid=%d: UndoDebuggerSusp complete\n", self->threadId); -} - -/* - * Determine if a thread is suspended. - * - * As with all operations on foreign threads, the caller should hold - * the thread list lock before calling. - */ -bool dvmIsSuspended(Thread* thread) -{ - /* - * The thread could be: - * (1) Running happily. status is RUNNING, isSuspended is false, - * suspendCount is zero. Return "false". - * (2) Pending suspend. status is RUNNING, isSuspended is false, - * suspendCount is nonzero. Return "false". - * (3) Suspended. suspendCount is nonzero, and either (status is - * RUNNING and isSuspended is true) OR (status is !RUNNING). - * Return "true". - * (4) Waking up. suspendCount is zero, status is RUNNING and - * isSuspended is true. Return "false" (since it could change - * out from under us, unless we hold suspendCountLock). - */ - - return (thread->suspendCount != 0 && - ((thread->status == THREAD_RUNNING && thread->isSuspended) || - (thread->status != THREAD_RUNNING))); -} - -/* - * Wait until another thread self-suspends. This is specifically for - * synchronization between the JDWP thread and a thread that has decided - * to suspend itself after sending an event to the debugger. - * - * Threads that encounter "suspend all" events work as well -- the thread - * in question suspends everybody else and then itself. - * - * We can't hold a thread lock here or in the caller, because we could - * get here just before the to-be-waited-for-thread issues a "suspend all". - * There's an opportunity for badness if the thread we're waiting for exits - * and gets cleaned up, but since the thread in question is processing a - * debugger event, that's not really a possibility. (To avoid deadlock, - * it's important that we not be in THREAD_RUNNING while we wait.) - */ -void dvmWaitForSuspend(Thread* thread) -{ - Thread* self = dvmThreadSelf(); - - LOG_THREAD("threadid=%d: waiting for threadid=%d to sleep\n", - self->threadId, thread->threadId); - - assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState)); - assert(thread != self); - assert(self->status != THREAD_RUNNING); - - waitForThreadSuspend(self, thread); - - LOG_THREAD("threadid=%d: threadid=%d is now asleep\n", - self->threadId, thread->threadId); -} - -/* - * Check to see if we need to suspend ourselves. If so, go to sleep on - * a condition variable. - * - * Takes "self" as an argument as an optimization. Pass in NULL to have - * it do the lookup. - * - * Returns "true" if we suspended ourselves. - */ -bool dvmCheckSuspendPending(Thread* self) -{ - bool didSuspend; - - if (self == NULL) - self = dvmThreadSelf(); - - /* fast path: if count is zero, bail immediately */ - if (self->suspendCount == 0) - return false; - - lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */ - - assert(self->suspendCount >= 0); /* XXX: valid? useful? */ - - didSuspend = (self->suspendCount != 0); - self->isSuspended = true; - LOG_THREAD("threadid=%d: self-suspending\n", self->threadId); - while (self->suspendCount != 0) { - /* wait for wakeup signal; releases lock */ - int cc; - cc = pthread_cond_wait(&gDvm.threadSuspendCountCond, - &gDvm.threadSuspendCountLock); - assert(cc == 0); - } - assert(self->suspendCount == 0 && self->dbgSuspendCount == 0); - self->isSuspended = false; - LOG_THREAD("threadid=%d: self-reviving, status=%d\n", - self->threadId, self->status); - - unlockThreadSuspendCount(); - - return didSuspend; -} - -/* - * Update our status. - * - * The "self" argument, which may be NULL, is accepted as an optimization. - * - * Returns the old status. - */ -ThreadStatus dvmChangeStatus(Thread* self, ThreadStatus newStatus) -{ - ThreadStatus oldStatus; - - if (self == NULL) - self = dvmThreadSelf(); - - LOGVV("threadid=%d: (status %d -> %d)\n", - self->threadId, self->status, newStatus); - - oldStatus = self->status; - - if (newStatus == THREAD_RUNNING) { - /* - * Change our status to THREAD_RUNNING. The transition requires - * that we check for pending suspension, because the VM considers - * us to be "asleep" in all other states. - * - * We need to do the "suspend pending" check FIRST, because it grabs - * a lock that could be held by something that wants us to suspend. - * If we're in RUNNING it will wait for us, and we'll be waiting - * for the lock it holds. - */ - assert(self->status != THREAD_RUNNING); - - dvmCheckSuspendPending(self); - self->status = THREAD_RUNNING; - } else { - /* - * Change from one state to another, neither of which is - * THREAD_RUNNING. This is most common during system or thread - * initialization. - */ - self->status = newStatus; - } - - return oldStatus; -} - -/* - * Get a statically defined thread group from a field in the ThreadGroup - * Class object. Expected arguments are "mMain" and "mSystem". - */ -static Object* getStaticThreadGroup(const char* fieldName) -{ - StaticField* groupField; - Object* groupObj; - - groupField = dvmFindStaticField(gDvm.classJavaLangThreadGroup, - fieldName, "Ljava/lang/ThreadGroup;"); - if (groupField == NULL) { - LOGE("java.lang.ThreadGroup does not have an '%s' field\n", fieldName); - dvmThrowException("Ljava/lang/IncompatibleClassChangeError;", NULL); - return NULL; - } - groupObj = dvmGetStaticFieldObject(groupField); - if (groupObj == NULL) { - LOGE("java.lang.ThreadGroup.%s not initialized\n", fieldName); - dvmThrowException("Ljava/lang/InternalError;", NULL); - return NULL; - } - - return groupObj; -} -Object* dvmGetSystemThreadGroup(void) -{ - return getStaticThreadGroup("mSystem"); -} -Object* dvmGetMainThreadGroup(void) -{ - return getStaticThreadGroup("mMain"); -} - -/* - * Given a VMThread object, return the associated Thread*. - * - * NOTE: if the thread detaches, the struct Thread will disappear, and - * we will be touching invalid data. For safety, lock the thread list - * before calling this. - */ -Thread* dvmGetThreadFromThreadObject(Object* vmThreadObj) -{ - int vmData; - - vmData = dvmGetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData); - return (Thread*) vmData; -} - - -/* - * Conversion map for "nice" values. - * - * We use Android thread priority constants to be consistent with the rest - * of the system. In some cases adjacent entries may overlap. - */ -static const int kNiceValues[10] = { - ANDROID_PRIORITY_LOWEST, /* 1 (MIN_PRIORITY) */ - ANDROID_PRIORITY_BACKGROUND + 6, - ANDROID_PRIORITY_BACKGROUND + 3, - ANDROID_PRIORITY_BACKGROUND, - ANDROID_PRIORITY_NORMAL, /* 5 (NORM_PRIORITY) */ - ANDROID_PRIORITY_NORMAL - 2, - ANDROID_PRIORITY_NORMAL - 4, - ANDROID_PRIORITY_URGENT_DISPLAY + 3, - ANDROID_PRIORITY_URGENT_DISPLAY + 2, - ANDROID_PRIORITY_URGENT_DISPLAY /* 10 (MAX_PRIORITY) */ -}; - -/* - * Change the priority of a system thread to match that of the Thread object. - * - * We map a priority value from 1-10 to Linux "nice" values, where lower - * numbers indicate higher priority. - */ -void dvmChangeThreadPriority(Thread* thread, int newPriority) -{ - pid_t pid = thread->systemTid; - int newNice; - - if (newPriority < 1 || newPriority > 10) { - LOGW("bad priority %d\n", newPriority); - newPriority = 5; - } - newNice = kNiceValues[newPriority-1]; - - if (setpriority(PRIO_PROCESS, pid, newNice) != 0) { - char* str = dvmGetThreadName(thread); - LOGI("setPriority(%d) '%s' to prio=%d(n=%d) failed: %s\n", - pid, str, newPriority, newNice, strerror(errno)); - free(str); - } else { - LOGV("setPriority(%d) to prio=%d(n=%d)\n", - pid, newPriority, newNice); - } -} - -/* - * Get the thread priority for the current thread by querying the system. - * This is useful when attaching a thread through JNI. - * - * Returns a value from 1 to 10 (compatible with java.lang.Thread values). - */ -static int getThreadPriorityFromSystem(void) -{ - int i, sysprio, jprio; - - errno = 0; - sysprio = getpriority(PRIO_PROCESS, 0); - if (sysprio == -1 && errno != 0) { - LOGW("getpriority() failed: %s\n", strerror(errno)); - return THREAD_NORM_PRIORITY; - } - - jprio = THREAD_MIN_PRIORITY; - for (i = 0; i < NELEM(kNiceValues); i++) { - if (sysprio >= kNiceValues[i]) - break; - jprio++; - } - if (jprio > THREAD_MAX_PRIORITY) - jprio = THREAD_MAX_PRIORITY; - - return jprio; -} - - -/* - * Return true if the thread is on gDvm.threadList. - * Caller should not hold gDvm.threadListLock. - */ -bool dvmIsOnThreadList(const Thread* thread) -{ - bool ret = false; - - dvmLockThreadList(NULL); - if (thread == gDvm.threadList) { - ret = true; - } else { - ret = thread->prev != NULL || thread->next != NULL; - } - dvmUnlockThreadList(); - - return ret; -} - -/* - * Dump a thread to the log file -- just calls dvmDumpThreadEx() with an - * output target. - */ -void dvmDumpThread(Thread* thread, bool isRunning) -{ - DebugOutputTarget target; - - dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG); - dvmDumpThreadEx(&target, thread, isRunning); -} - -/* - * Print information about the specified thread. - * - * Works best when the thread in question is "self" or has been suspended. - * When dumping a separate thread that's still running, set "isRunning" to - * use a more cautious thread dump function. - */ -void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread, - bool isRunning) -{ - /* tied to ThreadStatus enum */ - static const char* kStatusNames[] = { - "ZOMBIE", "RUNNABLE", "TIMED_WAIT", "MONITOR", "WAIT", - "INITIALIZING", "STARTING", "NATIVE", "VMWAIT" - }; - Object* threadObj; - Object* groupObj; - StringObject* nameStr; - char* threadName = NULL; - char* groupName = NULL; - bool isDaemon; - int priority; // java.lang.Thread priority - int policy; // pthread policy - struct sched_param sp; // pthread scheduling parameters - - threadObj = thread->threadObj; - if (threadObj == NULL) { - LOGW("Can't dump thread %d: threadObj not set\n", thread->threadId); - return; - } - nameStr = (StringObject*) dvmGetFieldObject(threadObj, - gDvm.offJavaLangThread_name); - threadName = dvmCreateCstrFromString(nameStr); - - priority = dvmGetFieldInt(threadObj, gDvm.offJavaLangThread_priority); - isDaemon = dvmGetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon); - - if (pthread_getschedparam(pthread_self(), &policy, &sp) != 0) { - LOGW("Warning: pthread_getschedparam failed\n"); - policy = -1; - sp.sched_priority = -1; - } - - /* a null value for group is not expected, but deal with it anyway */ - groupObj = (Object*) dvmGetFieldObject(threadObj, - gDvm.offJavaLangThread_group); - if (groupObj != NULL) { - int offset = dvmFindFieldOffset(gDvm.classJavaLangThreadGroup, - "name", "Ljava/lang/String;"); - if (offset < 0) { - LOGW("Unable to find 'name' field in ThreadGroup\n"); - } else { - nameStr = (StringObject*) dvmGetFieldObject(groupObj, offset); - groupName = dvmCreateCstrFromString(nameStr); - } - } - if (groupName == NULL) - groupName = strdup("(BOGUS GROUP)"); - - assert(thread->status < NELEM(kStatusNames)); - dvmPrintDebugMessage(target, - "\"%s\"%s prio=%d tid=%d %s\n", - threadName, isDaemon ? " daemon" : "", - priority, thread->threadId, kStatusNames[thread->status]); - dvmPrintDebugMessage(target, - " | group=\"%s\" sCount=%d dsCount=%d s=%d obj=%p\n", - groupName, thread->suspendCount, thread->dbgSuspendCount, - thread->isSuspended, thread->threadObj); - dvmPrintDebugMessage(target, - " | sysTid=%d nice=%d sched=%d/%d handle=%d\n", - thread->systemTid, getpriority(PRIO_PROCESS, thread->systemTid), - policy, sp.sched_priority, (int)thread->handle); - -#ifdef WITH_MONITOR_TRACKING - if (!isRunning) { - LockedObjectData* lod = thread->pLockedObjects; - if (lod != NULL) - dvmPrintDebugMessage(target, " | monitors held:\n"); - else - dvmPrintDebugMessage(target, " | monitors held: <none>\n"); - while (lod != NULL) { - dvmPrintDebugMessage(target, " > %p[%d] (%s)\n", - lod->obj, lod->recursionCount, lod->obj->clazz->descriptor); - lod = lod->next; - } - } -#endif - - if (isRunning) - dvmDumpRunningThreadStack(target, thread); - else - dvmDumpThreadStack(target, thread); - - free(threadName); - free(groupName); - -} - -/* - * Get the name of a thread. - * - * For correctness, the caller should hold the thread list lock to ensure - * that the thread doesn't go away mid-call. - * - * Returns a newly-allocated string, or NULL if the Thread doesn't have a name. - */ -char* dvmGetThreadName(Thread* thread) -{ - StringObject* nameObj; - - if (thread->threadObj == NULL) { - LOGW("threadObj is NULL, name not available\n"); - return strdup("-unknown-"); - } - - nameObj = (StringObject*) - dvmGetFieldObject(thread->threadObj, gDvm.offJavaLangThread_name); - return dvmCreateCstrFromString(nameObj); -} - -/* - * Dump all threads to the log file -- just calls dvmDumpAllThreadsEx() with - * an output target. - */ -void dvmDumpAllThreads(bool grabLock) -{ - DebugOutputTarget target; - - dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG); - dvmDumpAllThreadsEx(&target, grabLock); -} - -/* - * Print information about all known threads. Assumes they have been - * suspended (or are in a non-interpreting state, e.g. WAIT or NATIVE). - * - * If "grabLock" is true, we grab the thread lock list. This is important - * to do unless the caller already holds the lock. - */ -void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool grabLock) -{ - Thread* thread; - - dvmPrintDebugMessage(target, "DALVIK THREADS:\n"); - - if (grabLock) - dvmLockThreadList(dvmThreadSelf()); - - thread = gDvm.threadList; - while (thread != NULL) { - dvmDumpThreadEx(target, thread, false); - - /* verify link */ - assert(thread->next == NULL || thread->next->prev == thread); - - thread = thread->next; - } - - if (grabLock) - dvmUnlockThreadList(); -} - -#ifdef WITH_MONITOR_TRACKING -/* - * Count up the #of locked objects in the current thread. - */ -static int getThreadObjectCount(const Thread* self) -{ - LockedObjectData* lod; - int count = 0; - - lod = self->pLockedObjects; - while (lod != NULL) { - count++; - lod = lod->next; - } - return count; -} - -/* - * Add the object to the thread's locked object list if it doesn't already - * exist. The most recently added object is the most likely to be released - * next, so we insert at the head of the list. - * - * If it already exists, we increase the recursive lock count. - * - * The object's lock may be thin or fat. - */ -void dvmAddToMonitorList(Thread* self, Object* obj, bool withTrace) -{ - LockedObjectData* newLod; - LockedObjectData* lod; - int* trace; - int depth; - - lod = self->pLockedObjects; - while (lod != NULL) { - if (lod->obj == obj) { - lod->recursionCount++; - LOGV("+++ +recursive lock %p -> %d\n", obj, lod->recursionCount); - return; - } - lod = lod->next; - } - - newLod = (LockedObjectData*) calloc(1, sizeof(LockedObjectData)); - if (newLod == NULL) { - LOGE("malloc failed on %d bytes\n", sizeof(LockedObjectData)); - return; - } - newLod->obj = obj; - newLod->recursionCount = 0; - - if (withTrace) { - trace = dvmFillInStackTraceRaw(self, &depth); - newLod->rawStackTrace = trace; - newLod->stackDepth = depth; - } - - newLod->next = self->pLockedObjects; - self->pLockedObjects = newLod; - - LOGV("+++ threadid=%d: added %p, now %d\n", - self->threadId, newLod, getThreadObjectCount(self)); -} - -/* - * Remove the object from the thread's locked object list. If the entry - * has a nonzero recursion count, we just decrement the count instead. - */ -void dvmRemoveFromMonitorList(Thread* self, Object* obj) -{ - LockedObjectData* lod; - LockedObjectData* prevLod; - - lod = self->pLockedObjects; - prevLod = NULL; - while (lod != NULL) { - if (lod->obj == obj) { - if (lod->recursionCount > 0) { - lod->recursionCount--; - LOGV("+++ -recursive lock %p -> %d\n", - obj, lod->recursionCount); - return; - } else { - break; - } - } - prevLod = lod; - lod = lod->next; - } - - if (lod == NULL) { - LOGW("BUG: object %p not found in thread's lock list\n", obj); - return; - } - if (prevLod == NULL) { - /* first item in list */ - assert(self->pLockedObjects == lod); - self->pLockedObjects = lod->next; - } else { - /* middle/end of list */ - prevLod->next = lod->next; - } - - LOGV("+++ threadid=%d: removed %p, now %d\n", - self->threadId, lod, getThreadObjectCount(self)); - free(lod->rawStackTrace); - free(lod); -} - -/* - * If the specified object is already in the thread's locked object list, - * return the LockedObjectData struct. Otherwise return NULL. - */ -LockedObjectData* dvmFindInMonitorList(const Thread* self, const Object* obj) -{ - LockedObjectData* lod; - - lod = self->pLockedObjects; - while (lod != NULL) { - if (lod->obj == obj) - return lod; - lod = lod->next; - } - return NULL; -} -#endif /*WITH_MONITOR_TRACKING*/ - - -/* - * GC helper functions - */ - -static void gcScanInterpStackReferences(Thread *thread) -{ - const u4 *framePtr; - - framePtr = (const u4 *)thread->curFrame; - while (framePtr != NULL) { - const StackSaveArea *saveArea; - const Method *method; - - saveArea = SAVEAREA_FROM_FP(framePtr); - method = saveArea->method; - if (method != NULL) { -#ifdef COUNT_PRECISE_METHODS - /* the GC is running, so no lock required */ - if (!dvmIsNativeMethod(method)) { - if (dvmPointerSetAddEntry(gDvm.preciseMethods, method)) - LOGI("Added %s.%s %p\n", - method->clazz->descriptor, method->name, method); - } -#endif - int i; - for (i = method->registersSize - 1; i >= 0; i--) { - u4 rval = *framePtr++; -//TODO: wrap markifobject in a macro that does pointer checks - if (rval != 0 && (rval & 0x3) == 0) { - dvmMarkIfObject((Object *)rval); - } - } - } - /* else this is a break frame; nothing to mark. - */ - - /* Don't fall into an infinite loop if things get corrupted. - */ - assert((uintptr_t)saveArea->prevFrame > (uintptr_t)framePtr || - saveArea->prevFrame == NULL); - framePtr = saveArea->prevFrame; - } -} - -static void gcScanReferenceTable(ReferenceTable *refTable) -{ - Object **op; - - //TODO: these asserts are overkill; turn them off when things stablize. - assert(refTable != NULL); - assert(refTable->table != NULL); - assert(refTable->nextEntry != NULL); - assert((uintptr_t)refTable->nextEntry >= (uintptr_t)refTable->table); - assert(refTable->nextEntry - refTable->table <= refTable->maxEntries); - - op = refTable->table; - while ((uintptr_t)op < (uintptr_t)refTable->nextEntry) { - dvmMarkObjectNonNull(*(op++)); - } -} - -/* - * Scan a Thread and mark any objects it references. - */ -static void gcScanThread(Thread *thread) -{ - assert(thread != NULL); - - /* - * The target thread must be suspended or in a state where it can't do - * any harm (e.g. in Object.wait()). The only exception is the current - * thread, which will still be active and in the "running" state. - * - * (Newly-created threads shouldn't be able to shift themselves to - * RUNNING without a suspend-pending check, so this shouldn't cause - * a false-positive.) - */ - assert(thread->status != THREAD_RUNNING || thread->isSuspended || - thread == dvmThreadSelf()); - - HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_THREAD_OBJECT, thread->threadId); - - dvmMarkObject(thread->threadObj); // could be NULL, when constructing - - HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_NATIVE_STACK, thread->threadId); - - dvmMarkObject(thread->exception); // usually NULL - gcScanReferenceTable(&thread->internalLocalRefTable); - - HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JNI_LOCAL, thread->threadId); - - gcScanReferenceTable(&thread->jniLocalRefTable); - - if (thread->jniMonitorRefTable.table != NULL) { - HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JNI_MONITOR, thread->threadId); - - gcScanReferenceTable(&thread->jniMonitorRefTable); - } - - HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JAVA_FRAME, thread->threadId); - - gcScanInterpStackReferences(thread); - - HPROF_CLEAR_GC_SCAN_STATE(); -} - -static void gcScanAllThreads() -{ - Thread *thread; - - /* Lock the thread list so we can safely use the - * next/prev pointers. - */ - dvmLockThreadList(dvmThreadSelf()); - - for (thread = gDvm.threadList; thread != NULL; - thread = thread->next) - { - /* We need to scan our own stack, so don't special-case - * the current thread. - */ - gcScanThread(thread); - } - - dvmUnlockThreadList(); -} - -void dvmGcScanRootThreadGroups() -{ - /* We scan the VM's list of threads instead of going - * through the actual ThreadGroups, but it should be - * equivalent. - * - * This assumes that the ThreadGroup class object is in - * the root set, which should always be true; it's - * loaded by the built-in class loader, which is part - * of the root set. - */ - gcScanAllThreads(); -} - |
