summaryrefslogtreecommitdiffstats
path: root/vm/SignalCatcher.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/SignalCatcher.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/SignalCatcher.cpp')
-rw-r--r--vm/SignalCatcher.cpp324
1 files changed, 324 insertions, 0 deletions
diff --git a/vm/SignalCatcher.cpp b/vm/SignalCatcher.cpp
new file mode 100644
index 000000000..a8fc59dde
--- /dev/null
+++ b/vm/SignalCatcher.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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.
+ */
+
+/*
+ * This is a thread that catches signals and does something useful. For
+ * example, when a SIGQUIT (Ctrl-\) arrives, suspend the VM and dump the
+ * status of all threads.
+ */
+#include "Dalvik.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <cutils/open_memstream.h>
+
+static void* signalCatcherThreadStart(void* arg);
+
+/*
+ * Crank up the signal catcher thread.
+ *
+ * Returns immediately.
+ */
+bool dvmSignalCatcherStartup(void)
+{
+ gDvm.haltSignalCatcher = false;
+
+ if (!dvmCreateInternalThread(&gDvm.signalCatcherHandle,
+ "Signal Catcher", signalCatcherThreadStart, NULL))
+ return false;
+
+ return true;
+}
+
+/*
+ * Shut down the signal catcher thread if it was started.
+ *
+ * Since we know the thread is just sitting around waiting for signals
+ * to arrive, send it one.
+ */
+void dvmSignalCatcherShutdown(void)
+{
+ gDvm.haltSignalCatcher = true;
+ if (gDvm.signalCatcherHandle == 0) // not started yet
+ return;
+
+ pthread_kill(gDvm.signalCatcherHandle, SIGQUIT);
+
+ pthread_join(gDvm.signalCatcherHandle, NULL);
+ LOGV("signal catcher has shut down\n");
+}
+
+
+/*
+ * Print the name of the current process, if we can get it.
+ */
+static void printProcessName(const DebugOutputTarget* target)
+{
+ int fd = -1;
+
+ fd = open("/proc/self/cmdline", O_RDONLY, 0);
+ if (fd < 0)
+ goto bail;
+
+ char tmpBuf[256];
+ ssize_t actual;
+
+ actual = read(fd, tmpBuf, sizeof(tmpBuf)-1);
+ if (actual <= 0)
+ goto bail;
+
+ tmpBuf[actual] = '\0';
+ dvmPrintDebugMessage(target, "Cmd line: %s\n", tmpBuf);
+
+bail:
+ if (fd >= 0)
+ close(fd);
+}
+
+/*
+ * Dump the stack traces for all threads to the supplied file, putting
+ * a timestamp header on it.
+ */
+static void logThreadStacks(FILE* fp)
+{
+ DebugOutputTarget target;
+
+ dvmCreateFileOutputTarget(&target, fp);
+
+ pid_t pid = getpid();
+ time_t now = time(NULL);
+ struct tm* ptm;
+#ifdef HAVE_LOCALTIME_R
+ struct tm tmbuf;
+ ptm = localtime_r(&now, &tmbuf);
+#else
+ ptm = localtime(&now);
+#endif
+ dvmPrintDebugMessage(&target,
+ "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n",
+ pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday,
+ ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+ printProcessName(&target);
+ dvmPrintDebugMessage(&target, "\n");
+ dvmDumpAllThreadsEx(&target, true);
+ fprintf(fp, "----- end %d -----\n", pid);
+}
+
+
+/*
+ * Respond to a SIGQUIT by dumping the thread stacks. Optionally dump
+ * a few other things while we're at it.
+ *
+ * Thread stacks can either go to the log or to a file designated for holding
+ * ANR traces. If we're writing to a file, we want to do it in one shot,
+ * so we can use a single O_APPEND write instead of contending for exclusive
+ * access with flock(). There may be an advantage in resuming the VM
+ * before doing the file write, so we don't stall the VM if disk I/O is
+ * bottlenecked.
+ *
+ * If JIT tuning is compiled in, dump compiler stats as well.
+ */
+static void handleSigQuit(void)
+{
+ char* traceBuf = NULL;
+ size_t traceLen;
+
+ dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);
+
+ dvmDumpLoaderStats("sig");
+
+ if (gDvm.stackTraceFile == NULL) {
+ /* just dump to log */
+ DebugOutputTarget target;
+ dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
+ dvmDumpAllThreadsEx(&target, true);
+ } else {
+ /* write to memory buffer */
+ FILE* memfp = open_memstream(&traceBuf, &traceLen);
+ if (memfp == NULL) {
+ LOGE("Unable to create memstream for stack traces\n");
+ traceBuf = NULL; /* make sure it didn't touch this */
+ /* continue on */
+ } else {
+ logThreadStacks(memfp);
+ fclose(memfp);
+ }
+ }
+
+#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
+ dvmCompilerDumpStats();
+#endif
+
+ if (false) dvmDumpTrackedAllocations(true);
+
+ dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP);
+
+ if (traceBuf != NULL) {
+ /*
+ * We don't know how long it will take to do the disk I/O, so put us
+ * into VMWAIT for the duration.
+ */
+ ThreadStatus oldStatus = dvmChangeStatus(dvmThreadSelf(), THREAD_VMWAIT);
+
+ /*
+ * Open the stack trace output file, creating it if necessary. It
+ * needs to be world-writable so other processes can write to it.
+ */
+ int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
+ if (fd < 0) {
+ LOGE("Unable to open stack trace file '%s': %s\n",
+ gDvm.stackTraceFile, strerror(errno));
+ } else {
+ ssize_t actual = write(fd, traceBuf, traceLen);
+ if (actual != (ssize_t) traceLen) {
+ LOGE("Failed to write stack traces to %s (%d of %zd): %s\n",
+ gDvm.stackTraceFile, (int) actual, traceLen,
+ strerror(errno));
+ } else {
+ LOGI("Wrote stack traces to '%s'\n", gDvm.stackTraceFile);
+ }
+ close(fd);
+ }
+
+ free(traceBuf);
+ dvmChangeStatus(dvmThreadSelf(), oldStatus);
+ }
+}
+
+/*
+ * Respond to a SIGUSR1 by forcing a GC.
+ */
+static void handleSigUsr1(void)
+{
+ LOGI("SIGUSR1 forcing GC (no HPROF)\n");
+ dvmCollectGarbage();
+}
+
+#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
+/* Sample callback function for dvmJitScanAllClassPointers */
+void printAllClass(void *ptr)
+{
+ ClassObject **classPP = (ClassObject **) ptr;
+ LOGE("class %s", (*classPP)->descriptor);
+
+}
+
+/*
+ * Respond to a SIGUSR2 by dumping some JIT stats and possibly resetting
+ * the code cache.
+ */
+static void handleSigUsr2(void)
+{
+ static int codeCacheResetCount = 0;
+ gDvmJit.receivedSIGUSR2 ^= true;
+ if ((--codeCacheResetCount & 7) == 0) {
+ /* Dump all class pointers in the traces */
+ dvmJitScanAllClassPointers(printAllClass);
+ gDvmJit.codeCacheFull = true;
+ } else {
+ dvmCompilerDumpStats();
+ /* Stress-test unchain all */
+ dvmJitUnchainAll();
+ LOGD("Send %d more signals to reset the code cache",
+ codeCacheResetCount & 7);
+ }
+ dvmCheckInterpStateConsistency();
+}
+#endif
+
+/*
+ * Sleep in sigwait() until a signal arrives.
+ */
+static void* signalCatcherThreadStart(void* arg)
+{
+ Thread* self = dvmThreadSelf();
+ sigset_t mask;
+ int cc;
+
+ UNUSED_PARAMETER(arg);
+
+ LOGV("Signal catcher thread started (threadid=%d)\n", self->threadId);
+
+ /* set up mask with signals we want to handle */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGQUIT);
+ sigaddset(&mask, SIGUSR1);
+#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
+ sigaddset(&mask, SIGUSR2);
+#endif
+
+ while (true) {
+ int rcvd;
+
+ dvmChangeStatus(self, THREAD_VMWAIT);
+
+ /*
+ * Signals for sigwait() must be blocked but not ignored. We
+ * block signals like SIGQUIT for all threads, so the condition
+ * is met. When the signal hits, we wake up, without any signal
+ * handlers being invoked.
+ *
+ * When running under GDB we occasionally return from sigwait()
+ * with EINTR (e.g. when other threads exit).
+ */
+loop:
+ cc = sigwait(&mask, &rcvd);
+ if (cc != 0) {
+ if (cc == EINTR) {
+ //LOGV("sigwait: EINTR\n");
+ goto loop;
+ }
+ assert(!"bad result from sigwait");
+ }
+
+ if (!gDvm.haltSignalCatcher) {
+ LOGI("threadid=%d: reacting to signal %d\n",
+ dvmThreadSelf()->threadId, rcvd);
+ }
+
+ /* set our status to RUNNING, self-suspending if GC in progress */
+ dvmChangeStatus(self, THREAD_RUNNING);
+
+ if (gDvm.haltSignalCatcher)
+ break;
+
+ switch (rcvd) {
+ case SIGQUIT:
+ handleSigQuit();
+ break;
+ case SIGUSR1:
+ handleSigUsr1();
+ break;
+#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
+ case SIGUSR2:
+ handleSigUsr2();
+ break;
+#endif
+ default:
+ LOGE("unexpected signal %d\n", rcvd);
+ break;
+ }
+ }
+
+ return NULL;
+}