/* * Dump the contents of a raw stack trace to the log. */ void dvmLogRawStackTrace(const int* intVals, int stackDepth) { int i; /* * Run through the array of stack frame data. */ for (i = 0; i < stackDepth; i++) { Method* meth; int lineNumber, pc; const char* sourceFile; char* dotName; meth = (Method*) *intVals++; pc = *intVals++; if (pc == -1) // broken top frame? lineNumber = 0; else lineNumber = dvmLineNumFromPC(meth, pc); // probably don't need to do this, but it looks nicer dotName = dvmDescriptorToDot(meth->clazz->descriptor); if (dvmIsNativeMethod(meth)) { LOGI("\tat %s.%s(Native Method)\n", dotName, meth->name); } else { LOGI("\tat %s.%s(%s:%d)\n", dotName, meth->name, dvmGetMethodSourceFile(meth), dvmLineNumFromPC(meth, pc)); } free(dotName); sourceFile = dvmGetMethodSourceFile(meth); } }
/* * 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(); } }
/* * Lock a monitor. */ static void lockMonitor(Thread* self, Monitor* mon) { ThreadStatus oldStatus; u4 waitThreshold, samplePercent; u8 waitStart, waitEnd, waitMs; if (mon->owner == self) { mon->lockCount++; return; } if (dvmTryLockMutex(&mon->lock) != 0) { oldStatus = dvmChangeStatus(self, THREAD_MONITOR); waitThreshold = gDvm.lockProfThreshold; if (waitThreshold) { waitStart = dvmGetRelativeTimeUsec(); } const Method* currentOwnerMethod = mon->ownerMethod; u4 currentOwnerPc = mon->ownerPc; dvmLockMutex(&mon->lock); if (waitThreshold) { waitEnd = dvmGetRelativeTimeUsec(); } dvmChangeStatus(self, oldStatus); if (waitThreshold) { waitMs = (waitEnd - waitStart) / 1000; if (waitMs >= waitThreshold) { samplePercent = 100; } else { samplePercent = 100 * waitMs / waitThreshold; } if (samplePercent != 0 && ((u4)rand() % 100 < samplePercent)) { const char* currentOwnerFileName = "no_method"; u4 currentOwnerLineNumber = 0; if (currentOwnerMethod != NULL) { currentOwnerFileName = dvmGetMethodSourceFile(currentOwnerMethod); if (currentOwnerFileName == NULL) { currentOwnerFileName = "no_method_file"; } currentOwnerLineNumber = dvmLineNumFromPC(currentOwnerMethod, currentOwnerPc); } logContentionEvent(self, waitMs, samplePercent, currentOwnerFileName, currentOwnerLineNumber); } } } mon->owner = self; assert(mon->lockCount == 0); // When debugging, save the current monitor holder for future // acquisition failures to use in sampled logging. if (gDvm.lockProfThreshold > 0) { mon->ownerMethod = NULL; mon->ownerPc = 0; if (self->interpSave.curFrame == NULL) { return; } const StackSaveArea* saveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame); if (saveArea == NULL) { return; } mon->ownerMethod = saveArea->method; mon->ownerPc = (saveArea->xtra.currentPc - saveArea->method->insns); } }
static void logContentionEvent(Thread *self, u4 waitMs, u4 samplePercent, const char *ownerFileName, u4 ownerLineNumber) { const StackSaveArea *saveArea; const Method *meth; u4 relativePc; char eventBuffer[174]; const char *fileName; char procName[33]; char *cp; size_t len; int fd; /* When a thread is being destroyed it is normal that the frame depth is zero */ if (self->interpSave.curFrame == NULL) { return; } saveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame); meth = saveArea->method; cp = eventBuffer; /* Emit the event list length, 1 byte. */ *cp++ = 9; /* Emit the process name, <= 37 bytes. */ fd = open("/proc/self/cmdline", O_RDONLY); memset(procName, 0, sizeof(procName)); read(fd, procName, sizeof(procName) - 1); close(fd); len = strlen(procName); cp = logWriteString(cp, procName, len); /* Emit the sensitive thread ("main thread") status, 5 bytes. */ bool isSensitive = false; if (gDvm.isSensitiveThreadHook != NULL) { isSensitive = gDvm.isSensitiveThreadHook(); } cp = logWriteInt(cp, isSensitive); /* Emit self thread name string, <= 37 bytes. */ std::string selfName = dvmGetThreadName(self); cp = logWriteString(cp, selfName.c_str(), selfName.size()); /* Emit the wait time, 5 bytes. */ cp = logWriteInt(cp, waitMs); /* Emit the source code file name, <= 37 bytes. */ fileName = dvmGetMethodSourceFile(meth); if (fileName == NULL) fileName = ""; cp = logWriteString(cp, fileName, strlen(fileName)); /* Emit the source code line number, 5 bytes. */ relativePc = saveArea->xtra.currentPc - saveArea->method->insns; cp = logWriteInt(cp, dvmLineNumFromPC(meth, relativePc)); /* Emit the lock owner source code file name, <= 37 bytes. */ if (ownerFileName == NULL) { ownerFileName = ""; } else if (strcmp(fileName, ownerFileName) == 0) { /* Common case, so save on log space. */ ownerFileName = "-"; } cp = logWriteString(cp, ownerFileName, strlen(ownerFileName)); /* Emit the source code line number, 5 bytes. */ cp = logWriteInt(cp, ownerLineNumber); /* Emit the sample percentage, 5 bytes. */ cp = logWriteInt(cp, samplePercent); assert((size_t)(cp - eventBuffer) <= sizeof(eventBuffer)); android_btWriteLog(EVENT_LOG_TAG_dvm_lock_sample, EVENT_TYPE_LIST, eventBuffer, (size_t)(cp - eventBuffer)); }
int hprofDumpStackFrames(hprof_context_t *ctx) { HashIter iter; hprof_record_t *rec = &ctx->curRec; dvmHashTableLock(gStackFrameHashTable); for (dvmHashIterBegin(gStackFrameHashTable, &iter); !dvmHashIterDone(&iter); dvmHashIterNext(&iter)) { const StackFrameEntry *stackFrameEntry; const Method *method; int pc; const char *sourceFile; ClassObject *clazz; int lineNum; hprofStartNewRecord(ctx, HPROF_TAG_STACK_FRAME, HPROF_TIME); stackFrameEntry = (const StackFrameEntry *) dvmHashIterData(&iter); assert(stackFrameEntry != NULL); method = stackFrameEntry->frame.method; pc = stackFrameEntry->frame.pc; sourceFile = dvmGetMethodSourceFile(method); if (sourceFile == NULL) { sourceFile = "<unknown>"; lineNum = 0; } else { lineNum = dvmLineNumFromPC(method, pc); } clazz = (ClassObject *) hprofLookupClassId(method->clazz); /* STACK FRAME format: * * ID: ID for this stack frame * ID: ID for the method name * ID: ID for the method descriptor * ID: ID for the source file name * u4: class serial number * u4: line number, 0 = no line information * * We use the address of the stack frame as its ID. */ DexStringCache cache; const char* descriptor; dexStringCacheInit(&cache); descriptor = dexProtoGetMethodDescriptor(&method->prototype, &cache); hprofAddIdToRecord(rec, (u4) stackFrameEntry); hprofAddIdToRecord(rec, hprofLookupStringId(method->name)); hprofAddIdToRecord(rec, hprofLookupStringId(descriptor)); hprofAddIdToRecord(rec, hprofLookupStringId(sourceFile)); hprofAddU4ToRecord(rec, (u4) clazz->serialNumber); hprofAddU4ToRecord(rec, (u4) lineNum); dexStringCacheRelease(&cache); } dvmHashTableUnlock(gStackFrameHashTable); return 0; }
/* * 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"); }
/* * Generate an array of StackTraceElement objects from the raw integer * data encoded by dvmFillInStackTrace(). * * "intVals" points to the first {method,pc} pair. * * The returned array is not added to the "local refs" list. */ ArrayObject* dvmGetStackTraceRaw(const int* intVals, int stackDepth) { ArrayObject* steArray = NULL; int i; /* init this if we haven't yet */ if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement)) dvmInitClass(gDvm.classJavaLangStackTraceElement); /* allocate a StackTraceElement array */ steArray = dvmAllocArray(gDvm.classJavaLangStackTraceElementArray, stackDepth, kObjectArrayRefWidth, ALLOC_DEFAULT); if (steArray == NULL) goto bail; /* * Allocate and initialize a StackTraceElement for each stack frame. * We use the standard constructor to configure the object. */ for (i = 0; i < stackDepth; i++) { Object* ste; Method* meth; StringObject* className; StringObject* methodName; StringObject* fileName; int lineNumber, pc; const char* sourceFile; char* dotName; ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT); if (ste == NULL) goto bail; meth = (Method*) *intVals++; pc = *intVals++; if (pc == -1) // broken top frame? lineNumber = 0; else lineNumber = dvmLineNumFromPC(meth, pc); dotName = dvmDescriptorToDot(meth->clazz->descriptor); className = dvmCreateStringFromCstr(dotName); free(dotName); methodName = dvmCreateStringFromCstr(meth->name); sourceFile = dvmGetMethodSourceFile(meth); if (sourceFile != NULL) fileName = dvmCreateStringFromCstr(sourceFile); else fileName = NULL; /* * Invoke: * public StackTraceElement(String declaringClass, String methodName, * String fileName, int lineNumber) * (where lineNumber==-2 means "native") */ JValue unused; dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init, ste, &unused, className, methodName, fileName, lineNumber); dvmReleaseTrackedAlloc(ste, NULL); dvmReleaseTrackedAlloc((Object*) className, NULL); dvmReleaseTrackedAlloc((Object*) methodName, NULL); dvmReleaseTrackedAlloc((Object*) fileName, NULL); if (dvmCheckException(dvmThreadSelf())) goto bail; dvmSetObjectArrayElement(steArray, i, ste); } bail: dvmReleaseTrackedAlloc((Object*) steArray, NULL); return steArray; }