| /* |
| * 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> |
| |
| 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 log or to a file. If it's |
| * to a file we have a little setup to do. |
| */ |
| static void logThreadStacks(void) |
| { |
| DebugOutputTarget target; |
| |
| if (gDvm.stackTraceFile == NULL) { |
| /* just dump to log file */ |
| dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG); |
| dvmDumpAllThreadsEx(&target, true); |
| } else { |
| FILE* fp = NULL; |
| int cc, fd; |
| |
| /* |
| * Open the stack trace output file, creating it if necessary. It |
| * needs to be world-writable so other processes can write to it. |
| */ |
| 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)); |
| return; |
| } |
| |
| /* gain exclusive access to the file */ |
| cc = flock(fd, LOCK_EX | LOCK_UN); |
| if (cc != 0) { |
| LOGV("Sleeping on flock(%s)\n", gDvm.stackTraceFile); |
| cc = flock(fd, LOCK_EX); |
| } |
| if (cc != 0) { |
| LOGE("Unable to lock stack trace file '%s': %s\n", |
| gDvm.stackTraceFile, strerror(errno)); |
| close(fd); |
| return; |
| } |
| |
| fp = fdopen(fd, "a"); |
| if (fp == NULL) { |
| LOGE("Unable to fdopen '%s' (%d): %s\n", |
| gDvm.stackTraceFile, fd, strerror(errno)); |
| flock(fd, LOCK_UN); |
| close(fd); |
| return; |
| } |
| |
| 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"); |
| fflush(fp); /* emit at least the header if we crash during dump */ |
| dvmDumpAllThreadsEx(&target, true); |
| fprintf(fp, "----- end %d -----\n", pid); |
| |
| /* |
| * Unlock and close the file, flushing pending data before we unlock |
| * it. The fclose() will close the underyling fd. |
| */ |
| fflush(fp); |
| flock(fd, LOCK_UN); |
| fclose(fp); |
| |
| LOGI("Wrote stack trace to '%s'\n", gDvm.stackTraceFile); |
| } |
| } |
| |
| |
| /* |
| * 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. |
| * |
| * We want to suspend all other threads, so that it's safe to |
| * traverse their stacks. |
| * |
| * When running under GDB we occasionally return 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; |
| |
| if (rcvd == SIGQUIT) { |
| dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP); |
| dvmDumpLoaderStats("sig"); |
| |
| logThreadStacks(); |
| |
| #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) |
| dvmCompilerDumpStats(); |
| #endif |
| |
| if (false) { |
| dvmLockMutex(&gDvm.jniGlobalRefLock); |
| //dvmDumpReferenceTable(&gDvm.jniGlobalRefTable, "JNI global"); |
| dvmUnlockMutex(&gDvm.jniGlobalRefLock); |
| } |
| |
| //dvmDumpTrackedAllocations(true); |
| dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP); |
| } else if (rcvd == SIGUSR1) { |
| #if WITH_HPROF |
| LOGI("SIGUSR1 forcing GC and HPROF dump\n"); |
| hprofDumpHeap(NULL); |
| #else |
| LOGI("SIGUSR1 forcing GC (no HPROF)\n"); |
| dvmCollectGarbage(false); |
| #endif |
| #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) |
| } else if (rcvd == SIGUSR2) { |
| gDvmJit.printMe ^= true; |
| dvmCompilerDumpStats(); |
| /* Stress-test unchain all */ |
| dvmJitUnchainAll(); |
| #endif |
| } else { |
| LOGE("unexpected signal %d\n", rcvd); |
| } |
| } |
| |
| return NULL; |
| } |