1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* 18 * This is a thread that catches signals and does something useful. For 19 * example, when a SIGQUIT (Ctrl-\) arrives, suspend the VM and dump the 20 * status of all threads. 21 */ 22 #include "Dalvik.h" 23 24 #include <stdlib.h> 25 #include <unistd.h> 26 #include <signal.h> 27 #include <pthread.h> 28 #include <sys/file.h> 29 #include <sys/time.h> 30 #include <fcntl.h> 31 #include <errno.h> 32 33 #include <cutils/open_memstream.h> 34 35 static void* signalCatcherThreadStart(void* arg); 36 37 /* 38 * Crank up the signal catcher thread. 39 * 40 * Returns immediately. 41 */ 42 bool dvmSignalCatcherStartup() 43 { 44 gDvm.haltSignalCatcher = false; 45 46 if (!dvmCreateInternalThread(&gDvm.signalCatcherHandle, 47 "Signal Catcher", signalCatcherThreadStart, NULL)) 48 return false; 49 50 return true; 51 } 52 53 /* 54 * Shut down the signal catcher thread if it was started. 55 * 56 * Since we know the thread is just sitting around waiting for signals 57 * to arrive, send it one. 58 */ 59 void dvmSignalCatcherShutdown() 60 { 61 gDvm.haltSignalCatcher = true; 62 if (gDvm.signalCatcherHandle == 0) // not started yet 63 return; 64 65 pthread_kill(gDvm.signalCatcherHandle, SIGQUIT); 66 67 pthread_join(gDvm.signalCatcherHandle, NULL); 68 ALOGV("signal catcher has shut down"); 69 } 70 71 72 /* 73 * Print the name of the current process, if we can get it. 74 */ 75 static void printProcessName(const DebugOutputTarget* target) 76 { 77 int fd = -1; 78 79 fd = open("/proc/self/cmdline", O_RDONLY, 0); 80 if (fd < 0) 81 goto bail; 82 83 char tmpBuf[256]; 84 ssize_t actual; 85 86 actual = read(fd, tmpBuf, sizeof(tmpBuf)-1); 87 if (actual <= 0) 88 goto bail; 89 90 tmpBuf[actual] = '\0'; 91 dvmPrintDebugMessage(target, "Cmd line: %s\n", tmpBuf); 92 93 bail: 94 if (fd >= 0) 95 close(fd); 96 } 97 98 /* 99 * Dump the stack traces for all threads to the supplied file, putting 100 * a timestamp header on it. 101 */ 102 static void logThreadStacks(FILE* fp) 103 { 104 DebugOutputTarget target; 105 106 dvmCreateFileOutputTarget(&target, fp); 107 108 pid_t pid = getpid(); 109 time_t now = time(NULL); 110 struct tm* ptm; 111 #ifdef HAVE_LOCALTIME_R 112 struct tm tmbuf; 113 ptm = localtime_r(&now, &tmbuf); 114 #else 115 ptm = localtime(&now); 116 #endif 117 dvmPrintDebugMessage(&target, 118 "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n", 119 pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday, 120 ptm->tm_hour, ptm->tm_min, ptm->tm_sec); 121 printProcessName(&target); 122 dvmPrintDebugMessage(&target, "\n"); 123 dvmDumpJniStats(&target); 124 dvmDumpAllThreadsEx(&target, true); 125 fprintf(fp, "----- end %d -----\n", pid); 126 } 127 128 129 /* 130 * Respond to a SIGQUIT by dumping the thread stacks. Optionally dump 131 * a few other things while we're at it. 132 * 133 * Thread stacks can either go to the log or to a file designated for holding 134 * ANR traces. If we're writing to a file, we want to do it in one shot, 135 * so we can use a single O_APPEND write instead of contending for exclusive 136 * access with flock(). There may be an advantage in resuming the VM 137 * before doing the file write, so we don't stall the VM if disk I/O is 138 * bottlenecked. 139 * 140 * If JIT tuning is compiled in, dump compiler stats as well. 141 */ 142 static void handleSigQuit() 143 { 144 char* traceBuf = NULL; 145 size_t traceLen; 146 147 dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP); 148 149 dvmDumpLoaderStats("sig"); 150 151 if (gDvm.stackTraceFile == NULL) { 152 /* just dump to log */ 153 DebugOutputTarget target; 154 dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG); 155 dvmDumpJniStats(&target); 156 dvmDumpAllThreadsEx(&target, true); 157 } else { 158 /* write to memory buffer */ 159 FILE* memfp = open_memstream(&traceBuf, &traceLen); 160 if (memfp == NULL) { 161 ALOGE("Unable to create memstream for stack traces"); 162 traceBuf = NULL; /* make sure it didn't touch this */ 163 /* continue on */ 164 } else { 165 logThreadStacks(memfp); 166 fclose(memfp); 167 } 168 } 169 170 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) 171 dvmCompilerDumpStats(); 172 #endif 173 174 if (false) dvmDumpTrackedAllocations(true); 175 176 dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP); 177 178 if (traceBuf != NULL) { 179 /* 180 * We don't know how long it will take to do the disk I/O, so put us 181 * into VMWAIT for the duration. 182 */ 183 ThreadStatus oldStatus = dvmChangeStatus(dvmThreadSelf(), THREAD_VMWAIT); 184 185 /* 186 * Open the stack trace output file, creating it if necessary. It 187 * needs to be world-writable so other processes can write to it. 188 */ 189 int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666); 190 if (fd < 0) { 191 ALOGE("Unable to open stack trace file '%s': %s", 192 gDvm.stackTraceFile, strerror(errno)); 193 } else { 194 ssize_t actual = TEMP_FAILURE_RETRY(write(fd, traceBuf, traceLen)); 195 if (actual != (ssize_t) traceLen) { 196 ALOGE("Failed to write stack traces to %s (%d of %zd): %s", 197 gDvm.stackTraceFile, (int) actual, traceLen, 198 strerror(errno)); 199 } else { 200 ALOGI("Wrote stack traces to '%s'", gDvm.stackTraceFile); 201 } 202 close(fd); 203 } 204 205 free(traceBuf); 206 dvmChangeStatus(dvmThreadSelf(), oldStatus); 207 } 208 } 209 210 /* 211 * Respond to a SIGUSR1 by forcing a GC. 212 */ 213 static void handleSigUsr1() 214 { 215 ALOGI("SIGUSR1 forcing GC (no HPROF)"); 216 dvmCollectGarbage(); 217 } 218 219 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) 220 /* Sample callback function for dvmJitScanAllClassPointers */ 221 void printAllClass(void *ptr) 222 { 223 ClassObject **classPP = (ClassObject **) ptr; 224 ALOGE("class %s", (*classPP)->descriptor); 225 226 } 227 228 /* 229 * Respond to a SIGUSR2 by dumping some JIT stats and possibly resetting 230 * the code cache. 231 */ 232 static void handleSigUsr2() 233 { 234 static int codeCacheResetCount = 0; 235 gDvmJit.receivedSIGUSR2 ^= true; 236 if ((--codeCacheResetCount & 7) == 0) { 237 /* Dump all class pointers in the traces */ 238 dvmJitScanAllClassPointers(printAllClass); 239 gDvmJit.codeCacheFull = true; 240 } else { 241 dvmCompilerDumpStats(); 242 /* Stress-test unchain all */ 243 dvmJitUnchainAll(); 244 ALOGD("Send %d more signals to reset the code cache", 245 codeCacheResetCount & 7); 246 } 247 dvmCheckInterpStateConsistency(); 248 } 249 #endif 250 251 /* 252 * Sleep in sigwait() until a signal arrives. 253 */ 254 static void* signalCatcherThreadStart(void* arg) 255 { 256 Thread* self = dvmThreadSelf(); 257 sigset_t mask; 258 int cc; 259 260 UNUSED_PARAMETER(arg); 261 262 ALOGV("Signal catcher thread started (threadid=%d)", self->threadId); 263 264 /* set up mask with signals we want to handle */ 265 sigemptyset(&mask); 266 sigaddset(&mask, SIGQUIT); 267 sigaddset(&mask, SIGUSR1); 268 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) 269 sigaddset(&mask, SIGUSR2); 270 #endif 271 272 while (true) { 273 int rcvd; 274 275 dvmChangeStatus(self, THREAD_VMWAIT); 276 277 /* 278 * Signals for sigwait() must be blocked but not ignored. We 279 * block signals like SIGQUIT for all threads, so the condition 280 * is met. When the signal hits, we wake up, without any signal 281 * handlers being invoked. 282 * 283 * When running under GDB we occasionally return from sigwait() 284 * with EINTR (e.g. when other threads exit). 285 */ 286 loop: 287 cc = sigwait(&mask, &rcvd); 288 if (cc != 0) { 289 if (cc == EINTR) { 290 //ALOGV("sigwait: EINTR"); 291 goto loop; 292 } 293 assert(!"bad result from sigwait"); 294 } 295 296 if (!gDvm.haltSignalCatcher) { 297 ALOGI("threadid=%d: reacting to signal %d", 298 dvmThreadSelf()->threadId, rcvd); 299 } 300 301 /* set our status to RUNNING, self-suspending if GC in progress */ 302 dvmChangeStatus(self, THREAD_RUNNING); 303 304 if (gDvm.haltSignalCatcher) 305 break; 306 307 switch (rcvd) { 308 case SIGQUIT: 309 handleSigQuit(); 310 break; 311 case SIGUSR1: 312 handleSigUsr1(); 313 break; 314 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING) 315 case SIGUSR2: 316 handleSigUsr2(); 317 break; 318 #endif 319 default: 320 ALOGE("unexpected signal %d", rcvd); 321 break; 322 } 323 } 324 325 return NULL; 326 } 327