summaryrefslogtreecommitdiffstats
path: root/vm/Thread.cpp
diff options
context:
space:
mode:
authorCarl Shapiro <cshapiro@google.com>2011-04-15 18:38:06 -0700
committerCarl Shapiro <cshapiro@google.com>2011-04-15 21:18:10 -0700
commitd5c36b9040bd26a81219a7f399513526f9b46324 (patch)
tree921c49ef9ced8819389ef699ae61296741db71a5 /vm/Thread.cpp
parentc469fa622ebadfa3defc73a064e2e724f0ab7c75 (diff)
downloadandroid_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.cpp3554
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");
+}