diff options
author | Carl Shapiro <cshapiro@google.com> | 2011-04-15 18:38:06 -0700 |
---|---|---|
committer | Carl Shapiro <cshapiro@google.com> | 2011-04-15 21:18:10 -0700 |
commit | d5c36b9040bd26a81219a7f399513526f9b46324 (patch) | |
tree | 921c49ef9ced8819389ef699ae61296741db71a5 /vm/Thread.cpp | |
parent | c469fa622ebadfa3defc73a064e2e724f0ab7c75 (diff) | |
download | android_dalvik-d5c36b9040bd26a81219a7f399513526f9b46324.tar.gz android_dalvik-d5c36b9040bd26a81219a7f399513526f9b46324.tar.bz2 android_dalvik-d5c36b9040bd26a81219a7f399513526f9b46324.zip |
Move the remaining non-compiler VM code into C++.
Change-Id: Id8693208d2741c55a7b0474d1264f2112019d11f
Diffstat (limited to 'vm/Thread.cpp')
-rw-r--r-- | vm/Thread.cpp | 3554 |
1 files changed, 3554 insertions, 0 deletions
diff --git a/vm/Thread.cpp b/vm/Thread.cpp new file mode 100644 index 000000000..1754e0d1e --- /dev/null +++ b/vm/Thread.cpp @@ -0,0 +1,3554 @@ +/* + * 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/types.h> +#include <sys/resource.h> +#include <sys/mman.h> +#include <signal.h> +#include <errno.h> +#include <fcntl.h> + +#if defined(HAVE_PRCTL) +#include <sys/prctl.h> +#endif + +#if defined(WITH_SELF_VERIFICATION) +#include "interp/Jit.h" // need for self verification +#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 enable logging on cgroup errors +#define ENABLE_CGROUP_ERR_LOGGING 0 + +// 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 << 16) - 1) +#define kMainThreadId 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); + + /* + * 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; +} + +/* + * All threads should be stopped by now. Clean up some thread globals. + */ +void dvmThreadShutdown(void) +{ + if (gDvm.threadList != NULL) { + /* + * If we walk through the thread list and try to free the + * lingering thread structures (which should only be for daemon + * threads), the daemon threads may crash if they execute before + * the process dies. Let them leak. + */ + 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. + */ + dvmLockMutex(&gDvm.threadSuspendCountLock); +} + +/* + * 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. + * + * This function deliberately avoids the use of dvmChangeStatus(), + * which could grab threadSuspendCountLock. To avoid deadlock, threads + * are required to grab the thread list lock before the thread suspend + * count lock. (See comment in DvmGlobals.) + * + * TODO: consider checking for suspend after acquiring the lock, and + * backing off if set. As stated above, it can't happen during normal + * execution, but it *can* happen during shutdown when daemon threads + * are being suspended. + */ +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 during VM shutdown */ + oldStatus = THREAD_UNDEFINED; // shut up gcc + } + + dvmLockMutex(&gDvm.threadListLock); + + if (self != NULL) + self->status = oldStatus; +} + +/* + * Try to lock the thread list. + * + * Returns "true" if we locked it. This is a "fast" mutex, so if the + * current thread holds the lock this will fail. + */ +bool dvmTryLockThreadList(void) +{ + return (dvmTryLockMutex(&gDvm.threadListLock) == 0); +} + +/* + * Release the thread list global lock. + */ +void dvmUnlockThreadList(void) +{ + dvmUnlockMutex(&gDvm.threadListLock); +} + +/* + * Convert SuspendCause to a string. + */ +static const char* getSuspendCauseStr(SuspendCause why) +{ + switch (why) { + case SUSPEND_NOT: return "NOT?"; + case SUSPEND_FOR_GC: return "gc"; + case SUSPEND_FOR_DEBUG: return "debug"; + case SUSPEND_FOR_DEBUG_EVENT: return "debug-event"; + case SUSPEND_FOR_STACK_DUMP: return "stack-dump"; + case SUSPEND_FOR_VERIFY: return "verify"; + case SUSPEND_FOR_HPROF: return "hprof"; +#if defined(WITH_JIT) + case SUSPEND_FOR_TBL_RESIZE: return "table-resize"; + case SUSPEND_FOR_IC_PATCH: return "inline-cache-patch"; + case SUSPEND_FOR_CC_RESET: return "reset-code-cache"; + case SUSPEND_FOR_REFRESH: return "refresh jit status"; +#endif + default: return "UNKNOWN"; + } +} + +/* + * 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 kSpinSleepTime = 3*1000*1000; /* 3s */ + u8 startWhen = 0; // init req'd to placate gcc + int sleepIter = 0; + int cc; + + do { + cc = dvmTryLockMutex(&gDvm._threadSuspendLock); + if (cc != 0) { + Thread* self = dvmThreadSelf(); + + if (!dvmCheckSuspendPending(self)) { + /* + * Could be that a resume-all is in progress, and something + * grabbed the CPU when the wakeup was broadcast. The thread + * performing the resume hasn't had a chance to release the + * thread suspend lock. (We release before the broadcast, + * so this should be a narrow window.) + * + * Could be we hit the window as a suspend was started, + * and the lock has been grabbed but the suspend counts + * haven't been incremented yet. + * + * Could be an unusual JNI thread-attach thing. + * + * Could be the debugger telling us to resume at roughly + * the same time we're posting an event. + * + * Could be two app threads both want to patch predicted + * chaining cells around the same time. + */ + LOGI("threadid=%d ODD: want thread-suspend lock (%s:%s)," + " it's held, no suspend pending\n", + self->threadId, who, getSuspendCauseStr(why)); + } else { + /* we suspended; reset timeout */ + sleepIter = 0; + } + + /* 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:%s)," + " bailing\n", + self->threadId, who, getSuspendCauseStr(why)); + /* threads are not suspended, thread dump could crash */ + dvmDumpAllThreads(false); + dvmAbort(); + } + } + } while (cc != 0); + assert(cc == 0); +} + +/* + * Release the "thread suspend" lock. + */ +static inline void unlockThreadSuspend(void) +{ + dvmUnlockMutex(&gDvm._threadSuspendLock); +} + + +/* + * 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. + * + * This will be called from whatever thread calls DestroyJavaVM, usually + * but not necessarily the main thread. It's likely, but not guaranteed, + * that the current thread has already been cleaned up. + */ +void dvmSlayDaemons(void) +{ + Thread* self = dvmThreadSelf(); // may be null + Thread* target; + int threadId = 0; + bool doWait = false; + + dvmLockThreadList(self); + + if (self != NULL) + threadId = self->threadId; + + target = gDvm.threadList; + while (target != NULL) { + if (target == self) { + target = target->next; + continue; + } + + if (!dvmGetFieldBoolean(target->threadObj, + gDvm.offJavaLangThread_daemon)) + { + /* should never happen; suspend it with the rest */ + LOGW("threadid=%d: non-daemon id=%d still running at shutdown?!\n", + threadId, target->threadId); + } + + char* threadName = dvmGetThreadName(target); + LOGV("threadid=%d: suspending daemon id=%d name='%s'\n", + threadId, target->threadId, threadName); + free(threadName); + + /* mark as suspended */ + lockThreadSuspendCount(); + dvmAddToSuspendCounts(target, 1, 0); + unlockThreadSuspendCount(); + doWait = true; + + target = target->next; + } + + //dvmDumpAllThreads(false); + + /* + * Unlock the thread list, relocking it later if necessary. It's + * possible a thread is in VMWAIT after calling dvmLockThreadList, + * and that function *doesn't* check for pending suspend after + * acquiring the lock. We want to let them finish their business + * and see the pending suspend before we continue here. + * + * There's no guarantee of mutex fairness, so this might not work. + * (The alternative is to have dvmLockThreadList check for suspend + * after acquiring the lock and back off, something we should consider.) + */ + dvmUnlockThreadList(); + + if (doWait) { + bool complained = false; + + usleep(200 * 1000); + + dvmLockThreadList(self); + + /* + * Sleep for a bit until the threads have suspended. We're trying + * to exit, so don't wait for too long. + */ + int i; + for (i = 0; i < 10; i++) { + bool allSuspended = true; + + target = gDvm.threadList; + while (target != NULL) { + if (target == self) { + target = target->next; + continue; + } + + if (target->status == THREAD_RUNNING) { + if (!complained) + LOGD("threadid=%d not ready yet\n", target->threadId); + allSuspended = false; + /* keep going so we log each running daemon once */ + } + + target = target->next; + } + + if (allSuspended) { + LOGV("threadid=%d: all daemons have suspended\n", threadId); + break; + } else { + if (!complained) { + complained = true; + LOGD("threadid=%d: waiting briefly for daemon suspension\n", + threadId); + } + } + + usleep(200 * 1000); + } + dvmUnlockThreadList(); + } + +#if 0 /* bad things happen if they come out of JNI or "spuriously" wake up */ + /* + * Abandon the threads and recover their resources. + */ + target = gDvm.threadList; + while (target != NULL) { + Thread* nextTarget = target->next; + unlinkThread(target); + freeThread(target); + target = nextTarget; + } +#endif + + //dvmDumpAllThreads(true); +} + + +/* + * 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. + * This will execute some interpreted code (e.g. class initializers). + */ +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"); + 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 (at least, they would, + * if there were any). + */ + dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, + vmThreadObj); + + thread->threadObj = threadObj; + + /* + * Set the "context class loader" field in the system class loader. + * + * Retrieving the system class loader will cause invocation of + * ClassLoader.getSystemClassLoader(), which could conceivably call + * Thread.currentThread(), so we want the Thread to be fully configured + * before we do this. + */ + Object* systemLoader = dvmGetSystemClassLoader(); + if (systemLoader == NULL) { + LOGW("WARNING: system class loader is NULL (setting main ctxt)\n"); + /* keep going? */ + } else { + dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_contextClassLoader, + systemLoader); + dvmReleaseTrackedAlloc(systemLoader, NULL); + } + + /* include self in non-daemon threads (mainly for AttachCurrentThread) */ + gDvm.nonDaemonThreadCount++; + + return true; +} + + +/* + * Alloc and initialize a Thread struct. + * + * Does not create any objects, just stuff on the system (malloc) heap. + */ +static Thread* allocThread(int interpStackSize) +{ + Thread* thread; + u1* stackBottom; + + thread = (Thread*) calloc(1, sizeof(Thread)); + if (thread == NULL) + return NULL; + + /* Check sizes and alignment */ + assert((((uintptr_t)&thread->interpBreak.all) & 0x7) == 0); + assert(sizeof(thread->interpBreak) == sizeof(thread->interpBreak.all)); + + +#if defined(WITH_SELF_VERIFICATION) + if (dvmSelfVerificationShadowSpaceAlloc(thread) == NULL) + return NULL; +#endif + + assert(interpStackSize >= kMinStackSize && interpStackSize <=kMaxStackSize); + + thread->status = THREAD_INITIALIZING; + + /* + * 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) { +#if defined(WITH_SELF_VERIFICATION) + dvmSelfVerificationShadowSpaceFree(thread); +#endif + free(thread); + return NULL; + } + memset(stackBottom, 0xc5, interpStackSize); // stop valgrind complaints +#else + stackBottom = (u1*) mmap(NULL, interpStackSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (stackBottom == MAP_FAILED) { +#if defined(WITH_SELF_VERIFICATION) + dvmSelfVerificationShadowSpaceFree(thread); +#endif + 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; + +#ifndef DVM_NO_ASM_INTERP + thread->mainHandlerTable = dvmAsmInstructionStart; + thread->altHandlerTable = dvmAsmAltInstructionStart; + thread->interpBreak.ctl.curHandlerTable = thread->mainHandlerTable; +#endif + + /* give the thread code a chance to set things up */ + dvmInitInterpStack(thread, interpStackSize); + + /* One-time setup for interpreter/JIT state */ + dvmInitInterpreterState(thread); + + 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()); + /* + * If we were called by dvmAttachCurrentThread, the self value is + * already correctly established as "thread". + */ + setThreadSelf(thread); + + LOGV("threadid=%d: interp stack at %p\n", + thread->threadId, thread->interpStackStart - thread->interpStackSize); + + /* + * Initialize invokeReq. + */ + dvmInitMutex(&thread->invokeReq.lock); + pthread_cond_init(&thread->invokeReq.cv, NULL); + + /* + * Initialize our reference tracking tables. + * + * Most threads won't use jniMonitorRefTable, so we clear out the + * structure but don't call the init function (which allocs storage). + */ + if (!dvmInitIndirectRefTable(&thread->jniLocalRefTable, + kJniLocalRefMin, kJniLocalRefMax, kIndirectKindLocal)) + return false; + if (!dvmInitReferenceTable(&thread->internalLocalRefTable, + kInternalRefDefault, kInternalRefMax)) + return false; + + memset(&thread->jniMonitorRefTable, 0, sizeof(thread->jniMonitorRefTable)); + + pthread_cond_init(&thread->waitCond, NULL); + dvmInitMutex(&thread->waitMutex); + + /* Initialize safepoint callback mechanism */ + dvmInitMutex(&thread->callbackMutex); + + 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 + } + + dvmClearIndirectRefTable(&thread->jniLocalRefTable); + dvmClearReferenceTable(&thread->internalLocalRefTable); + if (&thread->jniMonitorRefTable.table != NULL) + dvmClearReferenceTable(&thread->jniMonitorRefTable); + +#if defined(WITH_SELF_VERIFICATION) + dvmSelfVerificationShadowSpaceFree(thread); +#endif + 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 will not 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. + * + * We could do the detach here instead of aborting, but this will lead to + * portability problems. Other implementations do not do this check and + * will simply be unaware that the thread has exited, leading to resource + * leaks (and, if this is a non-daemon thread, an infinite hang when the + * VM tries to shut down). + * + * Because some implementations may want to use the pthread destructor + * to initiate the detach, and the ordering of destructors is not defined, + * we want to iterate a couple of times to give those a chance to run. + */ +static void threadExitCheck(void* arg) +{ + const int kMaxCount = 2; + + Thread* self = (Thread*) arg; + assert(self != NULL); + + LOGV("threadid=%d: threadExitCheck(%p) count=%d\n", + self->threadId, arg, self->threadExitCheckCount); + + if (self->status == THREAD_ZOMBIE) { + LOGW("threadid=%d: Weird -- shouldn't be in threadExitCheck\n", + self->threadId); + return; + } + + if (self->threadExitCheckCount < kMaxCount) { + /* + * Spin a couple of times to let other destructors fire. + */ + LOGD("threadid=%d: thread exiting, not yet detached (count=%d)\n", + self->threadId, self->threadExitCheckCount); + self->threadExitCheckCount++; + int cc = pthread_setspecific(gDvm.pthreadKeySelf, self); + if (cc != 0) { + LOGE("threadid=%d: unable to re-add thread to TLS\n", + self->threadId); + dvmAbort(); + } + } else { + LOGE("threadid=%d: native thread exited without detaching\n", + self->threadId); + 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. + */ +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. + */ + 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; + + 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); + 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) +{ + /* + * 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. Also, overwriting + * the class' defining classloader pointer seems unwise. + * + * Instead, we save a pointer to the method and explicitly check for + * it in FindClass. The method is private so nobody else can call it. + */ + + assert(thread->threadId == kMainThreadId); /* main thread only */ + + if (!dvmPushJNIFrame(thread, gDvm.methDalvikSystemNativeStart_main)) + return false; + + /* + * Null out the "String[] args" argument. + */ + assert(gDvm.methDalvikSystemNativeStart_main->registersSize == 1); + u4* framePtr = (u4*) thread->curFrame; + framePtr[0] = 0; + + return true; +} + + +/* + * 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) +{ + return dvmPushJNIFrame(thread, gDvm.methDalvikSystemNativeStart_run); +} + +/* + * Helper function to set the name of the current thread + */ +static void setThreadName(const char *threadName) +{ + 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; + } +#if defined(HAVE_ANDROID_PTHREAD_SETNAME_NP) + /* pthread_setname_np fails rather than truncating long strings */ + char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic + strncpy(buf, s, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + int err = pthread_setname_np(pthread_self(), buf); + if (err != 0) { + LOGW("Unable to set the name of current thread to '%s': %s\n", + buf, strerror(err)); + } +#elif defined(HAVE_PRCTL) + prctl(PR_SET_NAME, (unsigned long) s, 0, 0, 0); +#else + LOGD("No way to set current thread's name (%s)\n", s); +#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. + * + * The "threadObj" reference must be pinned by the caller to prevent the GC + * from moving it around (e.g. added to the tracked allocation list). + */ +bool dvmCreateInterpThread(Object* threadObj, int reqStackSize) +{ + assert(threadObj != NULL); + + Thread* self = dvmThreadSelf(); + int stackSize; + if (reqStackSize == 0) + stackSize = gDvm.stackSize; + else if (reqStackSize < kMinStackSize) + stackSize = kMinStackSize; + else if (reqStackSize > kMaxStackSize) + stackSize = kMaxStackSize; + else + stackSize = reqStackSize; + + pthread_attr_t threadAttr; + 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. + */ + Object* vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT); + if (vmThreadObj == NULL) + return false; + + Thread* newThread = allocThread(stackSize); + if (newThread == NULL) { + dvmReleaseTrackedAlloc(vmThreadObj, NULL); + return false; + } + + 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(); + dvmThrowIllegalThreadStateException( + "thread has already been started"); + freeThread(newThread); + dvmReleaseTrackedAlloc(vmThreadObj, NULL); + } + + /* + * 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(); + + ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); + pthread_t threadHandle; + int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, + newThread); + dvmChangeStatus(self, oldStatus); + + if (cc != 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); + + dvmThrowOutOfMemoryError("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 (correct) sequence of events for a "badly timed" GC + * (where '-' is us, 'o' is the child, and '+' is some other thread): + * + * - 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; + + /* Add any existing global modes to the interpBreak control */ + dvmInitializeInterpBreak(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. + */ + dvmLockThreadList(self); + 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. + */ + 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). The thread state may change + * to VMWAIT briefly if network packets are sent. + */ + 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 = dvmGetFieldInt(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. + * + * This should only be called when an exception is pending. Before + * returning, the exception will be cleared. + */ +static void threadExitUncaughtException(Thread* self, Object* group) +{ + Object* exception; + Object* handlerObj; + Method* uncaughtHandler; + + 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); + assert(exception != NULL); + 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). + * The ThreadGroup will handle it directly or call the default + * uncaught exception handler. + */ + handlerObj = dvmGetFieldObject(self->threadObj, + gDvm.offJavaLangThread_uncaughtHandler); + if (handlerObj == NULL) + handlerObj = group; + + /* + * Find the "uncaughtException" method in this object. The method + * was declared in the Thread.UncaughtExceptionHandler interface. + */ + 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 { + /* should be impossible, but handle it anyway */ + LOGW("WARNING: no 'uncaughtException' method in class %s\n", + handlerObj->clazz->descriptor); + dvmSetException(self, exception); + dvmLogExceptionStackTrace(); + } + + /* if the uncaught handler threw, clear it */ + dvmClearException(self); + + dvmReleaseTrackedAlloc(exception, self); + + /* Remove this thread's suspendCount from global suspendCount sum */ + lockThreadSuspendCount(); + dvmAddToSuspendCounts(self, -self->interpBreak.ctl.suspendCount, 0); + unlockThreadSuspendCount(); +} + + +/* + * 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(); + ThreadStatus 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 = reinterpret_cast<jobject>(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; + + /* allocate thread struct, and establish a basic sense of self */ + self = allocThread(gDvm.stackSize); + if (self == NULL) + goto fail; + setThreadSelf(self); + + /* + * Finish our thread prep. We need to do this before adding ourselves + * to the thread list or 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. Once + * it's on the list the thread is visible to the JDWP code and the GC. + */ + LOG_THREAD("threadid=%d: adding to list (attached)\n", self->threadId); + + 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(); + + /* + * Switch state from initializing to running. + * + * It's possible that a GC began right before we added ourselves + * to the thread list, and is still going. That means our thread + * suspend count won't reflect the fact that we should be suspended. + * To deal with this, we transition to VMWAIT, pulse the heap lock, + * and then advance to RUNNING. That will ensure that we stall until + * the GC completes. + * + * Once we're in RUNNING, we're like any other thread in the VM (except + * for the lack of an initialized threadObj). We're then free to + * allocate and initialize objects. + */ + assert(self->status == THREAD_INITIALIZING); + dvmChangeStatus(self, THREAD_VMWAIT); + dvmLockMutex(&gDvm.gcHeapLock); + dvmUnlockMutex(&gDvm.gcHeapLock); + dvmChangeStatus(self, THREAD_RUNNING); + + /* + * Create Thread and VMThread objects. + */ + threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_DEFAULT); + vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT); + if (threadObj == NULL || vmThreadObj == NULL) + goto fail_unlink; + + /* + * This makes threadObj visible to the GC. We still have it in the + * tracked allocation table, so it can't move around on us. + */ + self->threadObj = threadObj; + dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)self); + + /* + * Create a string for the thread name. + */ + if (pArgs->name != NULL) { + threadNameStr = dvmCreateStringFromCstr(pArgs->name); + if (threadNameStr == NULL) { + assert(dvmCheckException(dvmThreadSelf())); + goto fail_unlink; + } + } + + init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>", + "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V"); + if (init == NULL) { + assert(dvmCheckException(self)); + goto fail_unlink; + } + + /* + * 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; + } + + /* + * 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. + * + * TODO: this still has a race, it's just smaller. Not sure this is + * worth putting effort into fixing. Need to hold a lock while + * fiddling with the field, or maybe initialize the Thread object in a + * way that ensures another thread can't call start() on it. + */ + if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) { + LOGW("WOW: thread start hijack\n"); + dvmThrowIllegalThreadStateException( + "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); + + /* we can now safely un-pin these */ + dvmReleaseTrackedAlloc(threadObj, self); + dvmReleaseTrackedAlloc(vmThreadObj, self); + dvmReleaseTrackedAlloc((Object*)threadNameStr, self); + + 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: + dvmReleaseTrackedAlloc(threadObj, self); + dvmReleaseTrackedAlloc(vmThreadObj, self); + dvmReleaseTrackedAlloc((Object*)threadNameStr, self); + 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((u4*)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. + */ + volatile void* raw = reinterpret_cast<volatile void*>(&self->status); + volatile int32_t* addr = reinterpret_cast<volatile int32_t*>(raw); + android_atomic_release_store(THREAD_VMWAIT, addr); + + /* + * 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) { + dvmWaitCond(&traceState->threadExitCond, + &traceState->startStopLock); + } + } + dvmUnlockMutex(&traceState->startStopLock); + + 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(); + dvmAddToSuspendCounts(thread, 1, 1); + + LOG_THREAD("threadid=%d: suspend++, now=%d\n", + thread->threadId, thread->interpBreak.ctl.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->interpBreak.ctl.suspendCount > 0) { + dvmAddToSuspendCounts(thread, -1, -1); + } else { + LOG_THREAD("threadid=%d: suspendCount already zero\n", + thread->threadId); + } + + LOG_THREAD("threadid=%d: suspend--, now=%d\n", + thread->threadId, thread->interpBreak.ctl.suspendCount); + + if (thread->interpBreak.ctl.suspendCount == 0) { + dvmBroadcastCond(&gDvm.threadSuspendCountCond); + } + + unlockThreadSuspendCount(); +} + +/* + * Suspend yourself, as a result of debugger activity. + */ +void dvmSuspendSelf(bool jdwpActivity) +{ + Thread* self = dvmThreadSelf(); + + /* debugger thread must 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(); + dvmAddToSuspendCounts(self, 1, 1); + + /* + * Suspend ourselves. + */ + assert(self->interpBreak.ctl.suspendCount > 0); + self->status = THREAD_SUSPENDED; + 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->interpBreak.ctl.suspendCount != 0) { + dvmWaitCond(&gDvm.threadSuspendCountCond, + &gDvm.threadSuspendCountLock); + if (self->interpBreak.ctl.suspendCount != 0) { + /* + * The condition was signaled but we're still suspended. This + * can happen if the debugger lets go while a SIGQUIT thread + * dump event is pending (assuming SignalCatcher was resumed for + * just long enough to try to grab the thread-suspend lock). + */ + LOGD("threadid=%d: still suspended after undo (sc=%d dc=%d)\n", + self->threadId, self->interpBreak.ctl.suspendCount, + self->interpBreak.ctl.dbgSuspendCount); + } + } + assert(self->interpBreak.ctl.suspendCount == 0 && + self->interpBreak.ctl.dbgSuspendCount == 0); + self->status = THREAD_RUNNING; + LOG_THREAD("threadid=%d: self-reviving (dbg), status=%d\n", + self->threadId, self->status); + + unlockThreadSuspendCount(); +} + +/* + * Dump the state of the current thread and that of another thread that + * we think is wedged. + */ +static void dumpWedgedThread(Thread* thread) +{ + dvmDumpThread(dvmThreadSelf(), false); + dvmPrintNativeBackTrace(); + + // dumping a running thread is risky, but could be useful + dvmDumpThread(thread, true); + + // stop now and get a core dump + //abort(); +} + +/* + * If the thread is running at below-normal priority, temporarily elevate + * it to "normal". + * + * Returns zero if no changes were made. Otherwise, returns bit flags + * indicating what was changed, storing the previous values in the + * provided locations. + */ +int dvmRaiseThreadPriorityIfNeeded(Thread* thread, int* pSavedThreadPrio, + SchedPolicy* pSavedThreadPolicy) +{ + errno = 0; + *pSavedThreadPrio = getpriority(PRIO_PROCESS, thread->systemTid); + if (errno != 0) { + LOGW("Unable to get priority for threadid=%d sysTid=%d\n", + thread->threadId, thread->systemTid); + return 0; + } + if (get_sched_policy(thread->systemTid, pSavedThreadPolicy) != 0) { + LOGW("Unable to get policy for threadid=%d sysTid=%d\n", + thread->threadId, thread->systemTid); + return 0; + } + + int changeFlags = 0; + + /* + * Change the priority if we're in the background group. + */ + if (*pSavedThreadPolicy == SP_BACKGROUND) { + if (set_sched_policy(thread->systemTid, SP_FOREGROUND) != 0) { + LOGW("Couldn't set fg policy on tid %d\n", thread->systemTid); + } else { + changeFlags |= kChangedPolicy; + LOGD("Temporarily moving tid %d to fg (was %d)\n", + thread->systemTid, *pSavedThreadPolicy); + } + } + + /* + * getpriority() returns the "nice" value, so larger numbers indicate + * lower priority, with 0 being normal. + */ + if (*pSavedThreadPrio > 0) { + const int kHigher = 0; + if (setpriority(PRIO_PROCESS, thread->systemTid, kHigher) != 0) { + LOGW("Couldn't raise priority on tid %d to %d\n", + thread->systemTid, kHigher); + } else { + changeFlags |= kChangedPriority; + LOGD("Temporarily raised priority on tid %d (%d -> %d)\n", + thread->systemTid, *pSavedThreadPrio, kHigher); + } + } + + return changeFlags; +} + +/* + * Reset the priority values for the thread in question. + */ +void dvmResetThreadPriority(Thread* thread, int changeFlags, + int savedThreadPrio, SchedPolicy savedThreadPolicy) +{ + if ((changeFlags & kChangedPolicy) != 0) { + if (set_sched_policy(thread->systemTid, savedThreadPolicy) != 0) { + LOGW("NOTE: couldn't reset tid %d to (%d)\n", + thread->systemTid, savedThreadPolicy); + } else { + LOGD("Restored policy of %d to %d\n", + thread->systemTid, savedThreadPolicy); + } + } + + if ((changeFlags & kChangedPriority) != 0) { + if (setpriority(PRIO_PROCESS, thread->systemTid, savedThreadPrio) != 0) + { + LOGW("NOTE: couldn't reset priority on thread %d to %d\n", + thread->systemTid, savedThreadPrio); + } else { + LOGD("Restored priority on %d to %d\n", + thread->systemTid, savedThreadPrio); + } + } +} + +/* + * 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. + */ +#define FIRST_SLEEP (250*1000) /* 0.25s */ +#define MORE_SLEEP (750*1000) /* 0.75s */ +static void waitForThreadSuspend(Thread* self, Thread* thread) +{ + const int kMaxRetries = 10; + int spinSleepTime = FIRST_SLEEP; + bool complained = false; + int priChangeFlags = 0; + int savedThreadPrio = -500; + SchedPolicy savedThreadPolicy = SP_FOREGROUND; + + int sleepIter = 0; + int retryCount = 0; + u8 startWhen = 0; // init req'd to placate gcc + u8 firstStartWhen = 0; + + while (thread->status == THREAD_RUNNING) { + if (sleepIter == 0) { // get current time on first iteration + startWhen = dvmGetRelativeTimeUsec(); + if (firstStartWhen == 0) // first iteration of first attempt + firstStartWhen = startWhen; + + /* + * After waiting for a bit, check to see if the target thread is + * running at a reduced priority. If so, bump it up temporarily + * to give it more CPU time. + */ + if (retryCount == 2) { + assert(thread->systemTid != 0); + priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread, + &savedThreadPrio, &savedThreadPolicy); + } + } + +#if defined (WITH_JIT) + /* + * If we're still waiting after the first timeout, unchain all + * translations iff: + * 1) There are new chains formed since the last unchain + * 2) The top VM frame of the running thread is running JIT'ed code + */ + if (gDvmJit.pJitEntryTable && retryCount > 0 && + gDvmJit.hasNewChain && thread->inJitCodeCache) { + LOGD("JIT unchain all for threadid=%d", thread->threadId); + dvmJitUnchainAll(); + } +#endif + + /* + * Sleep briefly. The iterative sleep call returns false if we've + * exceeded the total time limit for this round of sleeping. + */ + if (!dvmIterativeSleep(sleepIter++, spinSleepTime, startWhen)) { + if (spinSleepTime != FIRST_SLEEP) { + LOGW("threadid=%d: spin on suspend #%d threadid=%d (pcf=%d)\n", + self->threadId, retryCount, + thread->threadId, priChangeFlags); + if (retryCount > 1) { + /* stack trace logging is slow; skip on first iter */ + dumpWedgedThread(thread); + } + complained = true; + } + + // keep going; could be slow due to valgrind + sleepIter = 0; + spinSleepTime = MORE_SLEEP; + + if (retryCount++ == kMaxRetries) { + LOGE("Fatal spin-on-suspend, dumping threads\n"); + dvmDumpAllThreads(false); + + /* log this after -- long traces will scroll off log */ + LOGE("threadid=%d: stuck on threadid=%d, giving up\n", + self->threadId, thread->threadId); + + /* try to get a debuggerd dump from the spinning thread */ + dvmNukeThread(thread); + /* abort the VM */ + dvmAbort(); + } + } + } + + if (complained) { + LOGW("threadid=%d: spin on suspend resolved in %lld msec\n", + self->threadId, + (dvmGetRelativeTimeUsec() - firstStartWhen) / 1000); + //dvmDumpThread(thread, false); /* suspended, so dump is safe */ + } + if (priChangeFlags != 0) { + dvmResetThreadPriority(thread, priChangeFlags, savedThreadPrio, + savedThreadPolicy); + } +} + +/* + * 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. + * + * We know the current thread is in the thread list, because we attach the + * thread before doing anything that could cause VM suspension (like object + * allocation). + */ +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->interpBreak.ctl.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; + + dvmAddToSuspendCounts(thread, 1, + (why == SUSPEND_FOR_DEBUG || + why == SUSPEND_FOR_DEBUG_EVENT) + ? 1 : 0); + } + 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 sc=%d dc=%d\n", + self->threadId, + thread->threadId, thread->status, + thread->interpBreak.ctl.suspendCount, + thread->interpBreak.ctl.dbgSuspendCount); + } + + 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->interpBreak.ctl.suspendCount > 0) { + dvmAddToSuspendCounts(thread, -1, + (why == SUSPEND_FOR_DEBUG || + why == SUSPEND_FOR_DEBUG_EVENT) + ? -1 : 0); + } else { + LOG_THREAD("threadid=%d: suspendCount already zero\n", + thread->threadId); + } + } + unlockThreadSuspendCount(); + dvmUnlockThreadList(); + + /* + * In some ways it makes sense to continue to hold the thread-suspend + * lock while we issue the wakeup broadcast. It allows us to complete + * one operation before moving on to the next, which simplifies the + * thread activity debug traces. + * + * This approach caused us some difficulty under Linux, because the + * condition variable broadcast not only made the threads runnable, + * but actually caused them to execute, and it was a while before + * the thread performing the wakeup had an opportunity to release the + * thread-suspend lock. + * + * This is a problem because, when a thread tries to acquire that + * lock, it times out after 3 seconds. If at some point the thread + * is told to suspend, the clock resets; but since the VM is still + * theoretically mid-resume, there's no suspend pending. If, for + * example, the GC was waking threads up while the SIGQUIT handler + * was trying to acquire the lock, we would occasionally time out on + * a busy system and SignalCatcher would abort. + * + * We now perform the unlock before the wakeup broadcast. The next + * suspend can't actually start until the broadcast completes and + * returns, because we're holding the thread-suspend-count lock, but the + * suspending thread is now able to make progress and we avoid the abort. + * + * (Technically there is a narrow window between when we release + * the thread-suspend lock and grab the thread-suspend-count lock. + * This could cause us to send a broadcast to threads with nonzero + * suspend counts, but this is expected and they'll all just fall + * right back to sleep. It's probably safe to grab the suspend-count + * lock before releasing thread-suspend, since we're still following + * the correct order of acquisition, but it feels weird.) + */ + + LOG_THREAD("threadid=%d: ResumeAll waking others\n", self->threadId); + unlockThreadSuspend(); + + /* + * 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(); + + 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->interpBreak.ctl.dbgSuspendCount == 0); + continue; + } + + assert(thread->interpBreak.ctl.suspendCount >= + thread->interpBreak.ctl.dbgSuspendCount); + dvmAddToSuspendCounts(thread, + -thread->interpBreak.ctl.dbgSuspendCount, + -thread->interpBreak.ctl.dbgSuspendCount); + } + 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. + * + * If the thread is suspending or waking, these fields could be changing + * out from under us (or the thread could change state right after we + * examine it), making this generally unreliable. This is chiefly + * intended for use by the debugger. + */ +bool dvmIsSuspended(const Thread* thread) +{ + /* + * The thread could be: + * (1) Running happily. status is RUNNING, suspendCount is zero. + * Return "false". + * (2) Pending suspend. status is RUNNING, suspendCount is nonzero. + * Return "false". + * (3) Suspended. suspendCount is nonzero, and status is !RUNNING. + * Return "true". + * (4) Waking up. suspendCount is zero, status is SUSPENDED + * Return "false" (since it could change out from under us, unless + * we hold suspendCountLock). + */ + + return (thread->interpBreak.ctl.suspendCount != 0 && + 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. + * + * Returns "true" if we suspended ourselves. + */ +static bool fullSuspendCheck(Thread* self) +{ + assert(self != NULL); + assert(self->interpBreak.ctl.suspendCount >= 0); + + /* + * Grab gDvm.threadSuspendCountLock. This gives us exclusive write + * access to self->interpBreak.ctl.suspendCount. + */ + lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */ + + bool needSuspend = (self->interpBreak.ctl.suspendCount != 0); + if (needSuspend) { + LOG_THREAD("threadid=%d: self-suspending\n", self->threadId); + ThreadStatus oldStatus = self->status; /* should be RUNNING */ + self->status = THREAD_SUSPENDED; + + while (self->interpBreak.ctl.suspendCount != 0) { + /* + * Wait for wakeup signal, releasing lock. The act of releasing + * and re-acquiring the lock provides the memory barriers we + * need for correct behavior on SMP. + */ + dvmWaitCond(&gDvm.threadSuspendCountCond, + &gDvm.threadSuspendCountLock); + } + assert(self->interpBreak.ctl.suspendCount == 0 && + self->interpBreak.ctl.dbgSuspendCount == 0); + self->status = oldStatus; + LOG_THREAD("threadid=%d: self-reviving, status=%d\n", + self->threadId, self->status); + } + + unlockThreadSuspendCount(); + + return needSuspend; +} + +/* + * Check to see if a suspend is pending. If so, suspend the current + * thread, and return "true" after we have been resumed. + */ +bool dvmCheckSuspendPending(Thread* self) +{ + assert(self != NULL); + if (self->interpBreak.ctl.suspendCount == 0) { + return false; + } else { + return fullSuspendCheck(self); + } +} + +/* + * 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 (oldStatus == newStatus) + return oldStatus; + + 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, and another thread could + * be performing a GC now. + * + * The order of operations is very significant here. One way to + * do this wrong is: + * + * GCing thread Our thread (in NATIVE) + * ------------ ---------------------- + * check suspend count (== 0) + * dvmSuspendAllThreads() + * grab suspend-count lock + * increment all suspend counts + * release suspend-count lock + * check thread state (== NATIVE) + * all are suspended, begin GC + * set state to RUNNING + * (continue executing) + * + * We can correct this by grabbing the suspend-count lock and + * performing both of our operations (check suspend count, set + * state) while holding it, now we need to grab a mutex on every + * transition to RUNNING. + * + * What we do instead is change the order of operations so that + * the transition to RUNNING happens first. If we then detect + * that the suspend count is nonzero, we switch to SUSPENDED. + * + * Appropriate compiler and memory barriers are required to ensure + * that the operations are observed in the expected order. + * + * This does create a small window of opportunity where a GC in + * progress could observe what appears to be a running thread (if + * it happens to look between when we set to RUNNING and when we + * switch to SUSPENDED). At worst this only affects assertions + * and thread logging. (We could work around it with some sort + * of intermediate "pre-running" state that is generally treated + * as equivalent to running, but that doesn't seem worthwhile.) + * + * We can also solve this by combining the "status" and "suspend + * count" fields into a single 32-bit value. This trades the + * store/load barrier on transition to RUNNING for an atomic RMW + * op on all transitions and all suspend count updates (also, all + * accesses to status or the thread count require bit-fiddling). + * It also eliminates the brief transition through RUNNING when + * the thread is supposed to be suspended. This is possibly faster + * on SMP and slightly more correct, but less convenient. + */ + volatile void* raw = reinterpret_cast<volatile void*>(&self->status); + volatile int32_t* addr = reinterpret_cast<volatile int32_t*>(raw); + android_atomic_acquire_store(newStatus, addr); + if (self->interpBreak.ctl.suspendCount != 0) { + fullSuspendCheck(self); + } + } else { + /* + * Not changing to THREAD_RUNNING. No additional work required. + * + * We use a releasing store to ensure that, if we were RUNNING, + * any updates we previously made to objects on the managed heap + * will be observed before the state change. + */ + assert(newStatus != THREAD_SUSPENDED); + volatile void* raw = reinterpret_cast<volatile void*>(&self->status); + volatile int32_t* addr = reinterpret_cast<volatile int32_t*>(raw); + android_atomic_release_store(newStatus, addr); + } + + 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); + dvmThrowInternalError("bad definition for ThreadGroup"); + return NULL; + } + groupObj = dvmGetStaticFieldObject(groupField); + if (groupObj == NULL) { + LOGE("java.lang.ThreadGroup.%s not initialized\n", fieldName); + dvmThrowInternalError(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); + + if (false) { + Thread* thread = gDvm.threadList; + while (thread != NULL) { + if ((Thread*)vmData == thread) + break; + + thread = thread->next; + } + + if (thread == NULL) { + LOGW("WARNING: vmThreadObj=%p has thread=%p, not in thread list\n", + vmThreadObj, (Thread*)vmData); + vmData = 0; + } + } + + return (Thread*) vmData; +} + +/* + * Given a pthread handle, return the associated Thread*. + * Caller must hold the thread list lock. + * + * Returns NULL if the thread was not found. + */ +Thread* dvmGetThreadByHandle(pthread_t handle) +{ + Thread* thread; + for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { + if (thread->handle == handle) + break; + } + return thread; +} + +/* + * Given a threadId, return the associated Thread*. + * Caller must hold the thread list lock. + * + * Returns NULL if the thread was not found. + */ +Thread* dvmGetThreadByThreadId(u4 threadId) +{ + Thread* thread; + for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { + if (thread->threadId == threadId) + break; + } + return thread; +} + + +/* + * 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 (newNice >= ANDROID_PRIORITY_BACKGROUND) { + set_sched_policy(dvmGetSysThreadId(), SP_BACKGROUND); + } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) { + set_sched_policy(dvmGetSysThreadId(), SP_FOREGROUND); + } + + 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); +} + +/* + * Try to get the scheduler group. + * + * The data from /proc/<pid>/cgroup looks (something) like: + * 2:cpu:/bg_non_interactive + * 1:cpuacct:/ + * + * We return the part on the "cpu" line after the '/', which will be an + * empty string for the default cgroup. If the string is longer than + * "bufLen", the string will be truncated. + * + * On error, -1 is returned, and an error description will be stored in + * the buffer. + */ +static int getSchedulerGroup(int tid, char* buf, size_t bufLen) +{ +#ifdef HAVE_ANDROID_OS + char pathBuf[32]; + char lineBuf[256]; + FILE *fp; + + snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid); + if ((fp = fopen(pathBuf, "r")) == NULL) { + snprintf(buf, bufLen, "[fopen-error:%d]", errno); + return -1; + } + + while (fgets(lineBuf, sizeof(lineBuf) -1, fp) != NULL) { + char* subsys; + char* grp; + size_t len; + + /* Junk the first field */ + subsys = strchr(lineBuf, ':'); + if (subsys == NULL) { + goto out_bad_data; + } + + if (strncmp(subsys, ":cpu:", 5) != 0) { + /* Not the subsys we're looking for */ + continue; + } + + grp = strchr(subsys, '/'); + if (grp == NULL) { + goto out_bad_data; + } + grp++; /* Drop the leading '/' */ + + len = strlen(grp); + grp[len-1] = '\0'; /* Drop the trailing '\n' */ + + if (bufLen <= len) { + len = bufLen - 1; + } + strncpy(buf, grp, len); + buf[len] = '\0'; + fclose(fp); + return 0; + } + + snprintf(buf, bufLen, "[no-cpu-subsys]"); + fclose(fp); + return -1; + +out_bad_data: + LOGE("Bad cgroup data {%s}", lineBuf); + snprintf(buf, bufLen, "[data-parse-failed]"); + fclose(fp); + return -1; + +#else + snprintf(buf, bufLen, "[n/a]"); + return -1; +#endif +} + +/* + * Convert ThreadStatus to a string. + */ +const char* dvmGetThreadStatusStr(ThreadStatus status) +{ + switch (status) { + case THREAD_ZOMBIE: return "ZOMBIE"; + case THREAD_RUNNING: return "RUNNABLE"; + case THREAD_TIMED_WAIT: return "TIMED_WAIT"; + case THREAD_MONITOR: return "MONITOR"; + case THREAD_WAIT: return "WAIT"; + case THREAD_INITIALIZING: return "INITIALIZING"; + case THREAD_STARTING: return "STARTING"; + case THREAD_NATIVE: return "NATIVE"; + case THREAD_VMWAIT: return "VMWAIT"; + case THREAD_SUSPENDED: return "SUSPENDED"; + default: return "UNKNOWN"; + } +} + +/* + * 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) +{ + Object* threadObj; + Object* groupObj; + StringObject* nameStr; + char* threadName = NULL; + char* groupName = NULL; + char schedulerGroupBuf[32]; + bool isDaemon; + int priority; // java.lang.Thread priority + int policy; // pthread policy + struct sched_param sp; // pthread scheduling parameters + char schedstatBuf[64]; // contents of /proc/[pid]/task/[tid]/schedstat + + /* + * Get the java.lang.Thread object. This function gets called from + * some weird debug contexts, so it's possible that there's a GC in + * progress on some other thread. To decrease the chances of the + * thread object being moved out from under us, we add the reference + * to the tracked allocation list, which pins it in place. + * + * If threadObj is NULL, the thread is still in the process of being + * attached to the VM, and there's really nothing interesting to + * say about it yet. + */ + threadObj = thread->threadObj; + if (threadObj == NULL) { + LOGI("Can't dump thread %d: threadObj not set\n", thread->threadId); + return; + } + dvmAddTrackedAlloc(threadObj, NULL); + + 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; + } + if (getSchedulerGroup(thread->systemTid, schedulerGroupBuf, + sizeof(schedulerGroupBuf)) == 0 && + schedulerGroupBuf[0] == '\0') { + strcpy(schedulerGroupBuf, "default"); + } + + /* a null value for group is not expected, but deal with it anyway */ + groupObj = (Object*) dvmGetFieldObject(threadObj, + gDvm.offJavaLangThread_group); + if (groupObj != NULL) { + nameStr = (StringObject*) + dvmGetFieldObject(groupObj, gDvm.offJavaLangThreadGroup_name); + groupName = dvmCreateCstrFromString(nameStr); + } + if (groupName == NULL) + groupName = strdup("(null; initializing?)"); + + dvmPrintDebugMessage(target, + "\"%s\"%s prio=%d tid=%d %s%s\n", + threadName, isDaemon ? " daemon" : "", + priority, thread->threadId, dvmGetThreadStatusStr(thread->status), +#if defined(WITH_JIT) + thread->inJitCodeCache ? " JIT" : "" +#else + "" +#endif + ); + dvmPrintDebugMessage(target, + " | group=\"%s\" sCount=%d dsCount=%d obj=%p self=%p\n", + groupName, thread->interpBreak.ctl.suspendCount, + thread->interpBreak.ctl.dbgSuspendCount, thread->threadObj, thread); + dvmPrintDebugMessage(target, + " | sysTid=%d nice=%d sched=%d/%d cgrp=%s handle=%d\n", + thread->systemTid, getpriority(PRIO_PROCESS, thread->systemTid), + policy, sp.sched_priority, schedulerGroupBuf, (int)thread->handle); + + /* get some bits from /proc/self/stat */ + ProcStatData procStatData; + if (!dvmGetThreadStats(&procStatData, thread->systemTid)) { + /* failed, use zeroed values */ + memset(&procStatData, 0, sizeof(procStatData)); + } + + /* grab the scheduler stats for this thread */ + snprintf(schedstatBuf, sizeof(schedstatBuf), "/proc/self/task/%d/schedstat", + thread->systemTid); + int schedstatFd = open(schedstatBuf, O_RDONLY); + strcpy(schedstatBuf, "0 0 0"); /* show this if open/read fails */ + if (schedstatFd >= 0) { + ssize_t bytes; + bytes = read(schedstatFd, schedstatBuf, sizeof(schedstatBuf) - 1); + close(schedstatFd); + if (bytes >= 1) { + schedstatBuf[bytes-1] = '\0'; /* remove trailing newline */ + } + } + + /* show what we got */ + dvmPrintDebugMessage(target, + " | schedstat=( %s ) utm=%lu stm=%lu core=%d\n", + schedstatBuf, procStatData.utime, procStatData.stime, + procStatData.processor); + + if (isRunning) + dvmDumpRunningThreadStack(target, thread); + else + dvmDumpThreadStack(target, thread); + + dvmReleaseTrackedAlloc(threadObj, NULL); + 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"); + +#ifdef HAVE_ANDROID_OS + dvmPrintDebugMessage(target, + "(mutexes: tll=%x tsl=%x tscl=%x ghl=%x)\n", + gDvm.threadListLock.value, + gDvm._threadSuspendLock.value, + gDvm.threadSuspendCountLock.value, + gDvm.gcHeapLock.value); +#endif + + 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(); +} + +/* + * Nuke the target thread from orbit. + * + * The idea is to send a "crash" signal to the target thread so that + * debuggerd will take notice and dump an appropriate stack trace. + * Because of the way debuggerd works, we have to throw the same signal + * at it twice. + * + * This does not necessarily cause the entire process to stop, but once a + * thread has been nuked the rest of the system is likely to be unstable. + * This returns so that some limited set of additional operations may be + * performed, but it's advisable (and expected) to call dvmAbort soon. + * (This is NOT a way to simply cancel a thread.) + */ +void dvmNukeThread(Thread* thread) +{ + int killResult; + + /* suppress the heapworker watchdog to assist anyone using a debugger */ + gDvm.nativeDebuggerActive = true; + + /* + * Send the signals, separated by a brief interval to allow debuggerd + * to work its magic. An uncommon signal like SIGFPE or SIGSTKFLT + * can be used instead of SIGSEGV to avoid making it look like the + * code actually crashed at the current point of execution. + * + * (Observed behavior: with SIGFPE, debuggerd will dump the target + * thread and then the thread that calls dvmAbort. With SIGSEGV, + * you don't get the second stack trace; possibly something in the + * kernel decides that a signal has already been sent and it's time + * to just kill the process. The position in the current thread is + * generally known, so the second dump is not useful.) + * + * The target thread can continue to execute between the two signals. + * (The first just causes debuggerd to attach to it.) + */ + LOGD("threadid=%d: sending two SIGSTKFLTs to threadid=%d (tid=%d) to" + " cause debuggerd dump\n", + dvmThreadSelf()->threadId, thread->threadId, thread->systemTid); + killResult = pthread_kill(thread->handle, SIGSTKFLT); + if (killResult != 0) { + LOGD("NOTE: pthread_kill #1 failed: %s\n", strerror(killResult)); + } + usleep(2 * 1000 * 1000); // TODO: timed-wait until debuggerd attaches + killResult = pthread_kill(thread->handle, SIGSTKFLT); + if (killResult != 0) { + LOGD("NOTE: pthread_kill #2 failed: %s\n", strerror(killResult)); + } + LOGD("Sent, pausing to let debuggerd run\n"); + usleep(8 * 1000 * 1000); // TODO: timed-wait until debuggerd finishes + + /* ignore SIGSEGV so the eventual dmvAbort() doesn't notify debuggerd */ + signal(SIGSEGV, SIG_IGN); + LOGD("Continuing\n"); +} |