SignalCatcher.cpp revision 92c1f6f1b4249e4e379452ee7b49f027052bf4ce
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
35static void* signalCatcherThreadStart(void* arg);
36
37/*
38 * Crank up the signal catcher thread.
39 *
40 * Returns immediately.
41 */
42bool 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 */
59void 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 */
75static 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
93bail:
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 */
102static 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    dvmDumpAllThreadsEx(&target, true);
124    fprintf(fp, "----- end %d -----\n", pid);
125}
126
127
128/*
129 * Respond to a SIGQUIT by dumping the thread stacks.  Optionally dump
130 * a few other things while we're at it.
131 *
132 * Thread stacks can either go to the log or to a file designated for holding
133 * ANR traces.  If we're writing to a file, we want to do it in one shot,
134 * so we can use a single O_APPEND write instead of contending for exclusive
135 * access with flock().  There may be an advantage in resuming the VM
136 * before doing the file write, so we don't stall the VM if disk I/O is
137 * bottlenecked.
138 *
139 * If JIT tuning is compiled in, dump compiler stats as well.
140 */
141static void handleSigQuit()
142{
143    char* traceBuf = NULL;
144    size_t traceLen;
145
146    dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);
147
148    dvmDumpLoaderStats("sig");
149
150    if (gDvm.stackTraceFile == NULL) {
151        /* just dump to log */
152        DebugOutputTarget target;
153        dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
154        dvmDumpAllThreadsEx(&target, true);
155    } else {
156        /* write to memory buffer */
157        FILE* memfp = open_memstream(&traceBuf, &traceLen);
158        if (memfp == NULL) {
159            LOGE("Unable to create memstream for stack traces");
160            traceBuf = NULL;        /* make sure it didn't touch this */
161            /* continue on */
162        } else {
163            logThreadStacks(memfp);
164            fclose(memfp);
165        }
166    }
167
168#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
169    dvmCompilerDumpStats();
170#endif
171
172    if (false) dvmDumpTrackedAllocations(true);
173
174    dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP);
175
176    if (traceBuf != NULL) {
177        /*
178         * We don't know how long it will take to do the disk I/O, so put us
179         * into VMWAIT for the duration.
180         */
181        ThreadStatus oldStatus = dvmChangeStatus(dvmThreadSelf(), THREAD_VMWAIT);
182
183        /*
184         * Open the stack trace output file, creating it if necessary.  It
185         * needs to be world-writable so other processes can write to it.
186         */
187        int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
188        if (fd < 0) {
189            LOGE("Unable to open stack trace file '%s': %s",
190                gDvm.stackTraceFile, strerror(errno));
191        } else {
192            ssize_t actual = write(fd, traceBuf, traceLen);
193            if (actual != (ssize_t) traceLen) {
194                LOGE("Failed to write stack traces to %s (%d of %zd): %s",
195                    gDvm.stackTraceFile, (int) actual, traceLen,
196                    strerror(errno));
197            } else {
198                LOGI("Wrote stack traces to '%s'", gDvm.stackTraceFile);
199            }
200            close(fd);
201        }
202
203        free(traceBuf);
204        dvmChangeStatus(dvmThreadSelf(), oldStatus);
205    }
206}
207
208/*
209 * Respond to a SIGUSR1 by forcing a GC.
210 */
211static void handleSigUsr1()
212{
213    LOGI("SIGUSR1 forcing GC (no HPROF)");
214    dvmCollectGarbage();
215}
216
217#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
218/* Sample callback function for dvmJitScanAllClassPointers */
219void printAllClass(void *ptr)
220{
221    ClassObject **classPP = (ClassObject **) ptr;
222    LOGE("class %s", (*classPP)->descriptor);
223
224}
225
226/*
227 * Respond to a SIGUSR2 by dumping some JIT stats and possibly resetting
228 * the code cache.
229 */
230static void handleSigUsr2()
231{
232    static int codeCacheResetCount = 0;
233    gDvmJit.receivedSIGUSR2 ^= true;
234    if ((--codeCacheResetCount & 7) == 0) {
235        /* Dump all class pointers in the traces */
236        dvmJitScanAllClassPointers(printAllClass);
237        gDvmJit.codeCacheFull = true;
238    } else {
239        dvmCompilerDumpStats();
240        /* Stress-test unchain all */
241        dvmJitUnchainAll();
242        LOGD("Send %d more signals to reset the code cache",
243             codeCacheResetCount & 7);
244    }
245    dvmCheckInterpStateConsistency();
246}
247#endif
248
249/*
250 * Sleep in sigwait() until a signal arrives.
251 */
252static void* signalCatcherThreadStart(void* arg)
253{
254    Thread* self = dvmThreadSelf();
255    sigset_t mask;
256    int cc;
257
258    UNUSED_PARAMETER(arg);
259
260    ALOGV("Signal catcher thread started (threadid=%d)", self->threadId);
261
262    /* set up mask with signals we want to handle */
263    sigemptyset(&mask);
264    sigaddset(&mask, SIGQUIT);
265    sigaddset(&mask, SIGUSR1);
266#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
267    sigaddset(&mask, SIGUSR2);
268#endif
269
270    while (true) {
271        int rcvd;
272
273        dvmChangeStatus(self, THREAD_VMWAIT);
274
275        /*
276         * Signals for sigwait() must be blocked but not ignored.  We
277         * block signals like SIGQUIT for all threads, so the condition
278         * is met.  When the signal hits, we wake up, without any signal
279         * handlers being invoked.
280         *
281         * When running under GDB we occasionally return from sigwait()
282         * with EINTR (e.g. when other threads exit).
283         */
284loop:
285        cc = sigwait(&mask, &rcvd);
286        if (cc != 0) {
287            if (cc == EINTR) {
288                //ALOGV("sigwait: EINTR");
289                goto loop;
290            }
291            assert(!"bad result from sigwait");
292        }
293
294        if (!gDvm.haltSignalCatcher) {
295            LOGI("threadid=%d: reacting to signal %d",
296                dvmThreadSelf()->threadId, rcvd);
297        }
298
299        /* set our status to RUNNING, self-suspending if GC in progress */
300        dvmChangeStatus(self, THREAD_RUNNING);
301
302        if (gDvm.haltSignalCatcher)
303            break;
304
305        switch (rcvd) {
306        case SIGQUIT:
307            handleSigQuit();
308            break;
309        case SIGUSR1:
310            handleSigUsr1();
311            break;
312#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
313        case SIGUSR2:
314            handleSigUsr2();
315            break;
316#endif
317        default:
318            LOGE("unexpected signal %d", rcvd);
319            break;
320        }
321    }
322
323    return NULL;
324}
325