summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vm/Thread.c49
-rw-r--r--vm/Thread.h6
-rw-r--r--vm/alloc/HeapWorker.c3
3 files changed, 57 insertions, 1 deletions
diff --git a/vm/Thread.c b/vm/Thread.c
index 434369403..c5420a8c4 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -25,8 +25,10 @@
#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>
@@ -2667,9 +2669,16 @@ static void waitForThreadSuspend(Thread* self, Thread* thread)
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);
- dvmDumpAllThreads(false);
+
+ /* try to get a debuggerd dump from the spinning thread */
+ dvmNukeThread(thread);
+ /* abort the VM */
dvmAbort();
}
}
@@ -3571,6 +3580,44 @@ void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool 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 to abort soon. (This is NOT a way to
+ * simply cancel a thread.)
+ */
+void dvmNukeThread(Thread* thread)
+{
+ pid_t tid = thread->systemTid;
+
+ /*
+ * Send the signals, separated by a brief interval to allow debuggerd to
+ * work its magic. SIGFPE could be used to make it stand out a little
+ * in the crash dump. (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. The position in
+ * the current thread is generally know, so we're using SIGSEGV for now
+ * to reduce log volume.)
+ *
+ * The thread can continue to execute between the two signals. (The
+ * first just causes debuggerd to attach.)
+ */
+ LOGD("Sending two SIGSEGVs to tid=%d to cause debuggerd dump\n", tid);
+ kill(tid, SIGSEGV);
+ usleep(750 * 1000);
+ kill(tid, SIGSEGV);
+ usleep(1000 * 1000);
+ LOGD("Continuing\n");
+}
+
#ifdef WITH_MONITOR_TRACKING
/*
* Count up the #of locked objects in the current thread.
diff --git a/vm/Thread.h b/vm/Thread.h
index f397fba70..5ca73c453 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -517,6 +517,12 @@ void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread,
void dvmDumpAllThreads(bool grabLock);
void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool grabLock);
+/*
+ * Debug: kill a thread to get a debuggerd stack trace. Leaves the VM
+ * in an uncertain state.
+ */
+void dvmNukeThread(Thread* thread);
+
#ifdef WITH_MONITOR_TRACKING
/*
* Track locks held by the current thread, along with the stack trace at
diff --git a/vm/alloc/HeapWorker.c b/vm/alloc/HeapWorker.c
index 7d2b687b9..d743ce54c 100644
--- a/vm/alloc/HeapWorker.c
+++ b/vm/alloc/HeapWorker.c
@@ -180,6 +180,9 @@ void dvmAssertHeapWorkerThreadRunning()
free(desc);
dvmDumpAllThreads(true);
+ /* try to get a debuggerd dump from the target thread */
+ dvmNukeThread(thread);
+
/* abort the VM */
dvmAbort();
} else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) {