/* * 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"); dvmDumpJniStats(&target); dvmDumpAllThreadsEx(&target, true); fprintf(fp, "----- end %d -----\n", pid); }
/* * Dump the native stack for the specified thread. */ void dvmDumpNativeStack(const DebugOutputTarget* target, pid_t tid) { #ifdef HAVE_ANDROID_OS const size_t MAX_DEPTH = 32; backtrace_frame_t backtrace[MAX_DEPTH]; ssize_t frames = unwind_backtrace_thread(tid, backtrace, 0, MAX_DEPTH); if (frames > 0) { backtrace_symbol_t backtrace_symbols[MAX_DEPTH]; get_backtrace_symbols(backtrace, frames, backtrace_symbols); for (size_t i = 0; i < size_t(frames); i++) { char line[MAX_BACKTRACE_LINE_LENGTH]; format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i], line, MAX_BACKTRACE_LINE_LENGTH); dvmPrintDebugMessage(target, " %s\n", line); } free_backtrace_symbols(backtrace_symbols, frames); } else { dvmPrintDebugMessage(target, " (native backtrace unavailable)\n"); } #endif }
static void printWaitMessage(const DebugOutputTarget* target, const char* detail, Object* obj, Thread* thread) { std::string msg(StringPrintf(" - waiting %s <%p> ", detail, obj)); if (obj->clazz != gDvm.classJavaLangClass) { // I(16573) - waiting on <0xf5feda38> (a java.util.LinkedList) // I(16573) - waiting on <0xf5ed54f8> (a java.lang.Class<java.lang.ref.ReferenceQueue>) msg += "(a " + dvmHumanReadableType(obj) + ")"; } if (thread != NULL) { std::string threadName(dvmGetThreadName(thread)); StringAppendF(&msg, " held by tid=%d (%s)", thread->threadId, threadName.c_str()); } dvmPrintDebugMessage(target, "%s\n", msg.c_str()); }
/* * 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 for the specified thread, which is still running. * * This is very dangerous, because stack frames are being pushed on and * popped off, and if the thread exits we'll be looking at freed memory. * The plan here is to take a snapshot of the stack and then dump that * to try to minimize the chances of catching it mid-update. This should * work reasonably well on a single-CPU system. * * There is a small chance that calling here will crash the VM. */ void dvmDumpRunningThreadStack(const DebugOutputTarget* target, Thread* thread) { StackSaveArea* saveArea; const u1* origStack; u1* stackCopy = NULL; int origSize, fpOffset; void* fp; int depthLimit = 200; if (thread == NULL || thread->interpSave.curFrame == NULL) { dvmPrintDebugMessage(target, "DumpRunning: Thread at %p has no curFrame (threadid=%d)\n", thread, (thread != NULL) ? thread->threadId : 0); return; } /* wait for a full quantum */ sched_yield(); /* copy the info we need, then the stack itself */ origSize = thread->interpStackSize; origStack = (const u1*) thread->interpStackStart - origSize; stackCopy = (u1*) malloc(origSize); fpOffset = (u1*) thread->interpSave.curFrame - origStack; memcpy(stackCopy, origStack, origSize); /* * Run through the stack and rewrite the "prev" pointers. */ //ALOGI("DR: fpOff=%d (from %p %p)",fpOffset, origStack, // thread->interpSave.curFrame); fp = stackCopy + fpOffset; while (true) { int prevOffset; if (depthLimit-- < 0) { /* we're probably screwed */ dvmPrintDebugMessage(target, "DumpRunning: depth limit hit\n"); dvmAbort(); } saveArea = SAVEAREA_FROM_FP(fp); if (saveArea->prevFrame == NULL) break; prevOffset = (u1*) saveArea->prevFrame - origStack; if (prevOffset < 0 || prevOffset > origSize) { dvmPrintDebugMessage(target, "DumpRunning: bad offset found: %d (from %p %p)\n", prevOffset, origStack, saveArea->prevFrame); saveArea->prevFrame = NULL; break; } saveArea->prevFrame = (u4*)(stackCopy + prevOffset); fp = saveArea->prevFrame; } /* * We still need to pass the Thread for some monitor wait stuff. */ dumpFrames(target, stackCopy + fpOffset, thread); free(stackCopy); }
/* * Dump stack frames, starting from the specified frame and moving down. * * Each frame holds a pointer to the currently executing method, and the * saved program counter from the caller ("previous" frame). This means * we don't have the PC for the current method on the stack, which is * pretty reasonable since it's in the "PC register" for the VM. Because * exceptions need to show the correct line number we actually *do* have * an updated version in the fame's "xtra.currentPc", but it's unreliable. * * Note "framePtr" could be NULL in rare circumstances. */ static void dumpFrames(const DebugOutputTarget* target, void* framePtr, Thread* thread) { const StackSaveArea* saveArea; const Method* method; int checkCount = 0; const u2* currentPc = NULL; bool first = true; /* * We call functions that require us to be holding the thread list lock. * It's probable that the caller has already done so, but it's not * guaranteed. If it's not locked, lock it now. */ bool needThreadUnlock = dvmTryLockThreadList(); /* * The "currentPc" is updated whenever we execute an instruction that * might throw an exception. Show it here. */ if (framePtr != NULL && !dvmIsBreakFrame((u4*)framePtr)) { saveArea = SAVEAREA_FROM_FP(framePtr); if (saveArea->xtra.currentPc != NULL) currentPc = saveArea->xtra.currentPc; } while (framePtr != NULL) { saveArea = SAVEAREA_FROM_FP(framePtr); method = saveArea->method; if (dvmIsBreakFrame((u4*)framePtr)) { //dvmPrintDebugMessage(target, " (break frame)\n"); } else { int relPc; if (currentPc != NULL) relPc = currentPc - saveArea->method->insns; else relPc = -1; std::string methodName(dvmHumanReadableMethod(method, false)); if (dvmIsNativeMethod(method)) { dvmPrintDebugMessage(target, " at %s(Native Method)\n", methodName.c_str()); } else { dvmPrintDebugMessage(target, " at %s(%s:%s%d)\n", methodName.c_str(), dvmGetMethodSourceFile(method), (relPc >= 0 && first) ? "~" : "", relPc < 0 ? -1 : dvmLineNumFromPC(method, relPc)); } if (first) { /* * Decorate WAIT and MONITOR threads with some detail on * the first frame. * * warning: wait status not stable, even in suspend */ if (thread->status == THREAD_WAIT || thread->status == THREAD_TIMED_WAIT) { Monitor* mon = thread->waitMonitor; Object* obj = dvmGetMonitorObject(mon); if (obj != NULL) { Thread* joinThread = NULL; if (obj->clazz == gDvm.classJavaLangVMThread) { joinThread = dvmGetThreadFromThreadObject(obj); } if (joinThread == NULL) { joinThread = dvmGetObjectLockHolder(obj); } printWaitMessage(target, "on", obj, joinThread); } } else if (thread->status == THREAD_MONITOR) { Object* obj; Thread* owner; if (extractMonitorEnterObject(thread, &obj, &owner)) { printWaitMessage(target, "to lock", obj, owner); } } } } /* * Get saved PC for previous frame. There's no savedPc in a "break" * frame, because that represents native or interpreted code * invoked by the VM. The saved PC is sitting in the "PC register", * a local variable on the native stack. */ currentPc = saveArea->savedPc; first = false; if (saveArea->prevFrame != NULL && saveArea->prevFrame <= framePtr) { ALOGW("Warning: loop in stack trace at frame %d (%p -> %p)", checkCount, framePtr, saveArea->prevFrame); break; } framePtr = saveArea->prevFrame; checkCount++; if (checkCount > 300) { dvmPrintDebugMessage(target, " ***** printed %d frames, not showing any more\n", checkCount); break; } } if (needThreadUnlock) { dvmUnlockThreadList(); } }
/* * Dump stack frames, starting from the specified frame and moving down. * * Each frame holds a pointer to the currently executing method, and the * saved program counter from the caller ("previous" frame). This means * we don't have the PC for the current method on the stack, which is * pretty reasonable since it's in the "PC register" for the VM. Because * exceptions need to show the correct line number we actually *do* have * an updated version in the fame's "xtra.currentPc", but it's unreliable. * * Note "framePtr" could be NULL in rare circumstances. */ static void dumpFrames(const DebugOutputTarget* target, void* framePtr, Thread* thread) { const StackSaveArea* saveArea; const Method* method; int checkCount = 0; const u2* currentPc = NULL; bool first = true; /* * The "currentPc" is updated whenever we execute an instruction that * might throw an exception. Show it here. */ if (framePtr != NULL && !dvmIsBreakFrame(framePtr)) { saveArea = SAVEAREA_FROM_FP(framePtr); if (saveArea->xtra.currentPc != NULL) currentPc = saveArea->xtra.currentPc; } while (framePtr != NULL) { saveArea = SAVEAREA_FROM_FP(framePtr); method = saveArea->method; if (dvmIsBreakFrame(framePtr)) { //dvmPrintDebugMessage(target, " (break frame)\n"); } else { int relPc; if (currentPc != NULL) relPc = currentPc - saveArea->method->insns; else relPc = -1; char* className = dvmDescriptorToDot(method->clazz->descriptor); if (dvmIsNativeMethod(method)) dvmPrintDebugMessage(target, " at %s.%s(Native Method)\n", className, method->name); else { dvmPrintDebugMessage(target, " at %s.%s(%s:%s%d)\n", className, method->name, dvmGetMethodSourceFile(method), (relPc >= 0 && first) ? "~" : "", relPc < 0 ? -1 : dvmLineNumFromPC(method, relPc)); } free(className); if (first) { /* * Decorate WAIT and MONITOR threads with some detail on * the first frame. * * warning: wait status not stable, even in suspend */ if (thread->status == THREAD_WAIT || thread->status == THREAD_TIMED_WAIT) { Monitor* mon = thread->waitMonitor; Object* obj = dvmGetMonitorObject(mon); if (obj != NULL) { className = dvmDescriptorToDot(obj->clazz->descriptor); dvmPrintDebugMessage(target, " - waiting on <%p> (a %s)\n", obj, className); free(className); } } else if (thread->status == THREAD_MONITOR) { Object* obj; Thread* owner; if (extractMonitorEnterObject(thread, &obj, &owner)) { className = dvmDescriptorToDot(obj->clazz->descriptor); if (owner != NULL) { char* threadName = dvmGetThreadName(owner); dvmPrintDebugMessage(target, " - waiting to lock <%p> (a %s) held by threadid=%d (%s)\n", obj, className, owner->threadId, threadName); free(threadName); } else { dvmPrintDebugMessage(target, " - waiting to lock <%p> (a %s) held by ???\n", obj, className); } free(className); } } } } /* * Get saved PC for previous frame. There's no savedPc in a "break" * frame, because that represents native or interpreted code * invoked by the VM. The saved PC is sitting in the "PC register", * a local variable on the native stack. */ currentPc = saveArea->savedPc; first = false; if (saveArea->prevFrame != NULL && saveArea->prevFrame <= framePtr) { LOGW("Warning: loop in stack trace at frame %d (%p -> %p)\n", checkCount, framePtr, saveArea->prevFrame); break; } framePtr = saveArea->prevFrame; checkCount++; if (checkCount > 300) { dvmPrintDebugMessage(target, " ***** printed %d frames, not showing any more\n", checkCount); break; } } dvmPrintDebugMessage(target, "\n"); }
/* * 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); } }