/* * Generate the contents of a THST chunk. The data encompasses all known * threads. * * Response has: * (1b) header len * (1b) bytes per entry * (2b) thread count * Then, for each thread: * (4b) threadId * (1b) thread status * (4b) tid * (4b) utime * (4b) stime * (1b) is daemon? * * The length fields exist in anticipation of adding additional fields * without wanting to break ddms or bump the full protocol version. I don't * think it warrants full versioning. They might be extraneous and could * be removed from a future version. * * Returns a new byte[] with the data inside, or NULL on failure. The * caller must call dvmReleaseTrackedAlloc() on the array. */ ArrayObject* dvmDdmGenerateThreadStats(void) { const int kHeaderLen = 4; const int kBytesPerEntry = 18; dvmLockThreadList(NULL); Thread* thread; int threadCount = 0; for (thread = gDvm.threadList; thread != NULL; thread = thread->next) threadCount++; /* * Create a temporary buffer. We can't perform heap allocation with * the thread list lock held (could cause a GC). The output is small * enough to sit on the stack. */ int bufLen = kHeaderLen + threadCount * kBytesPerEntry; u1 tmpBuf[bufLen]; u1* buf = tmpBuf; set1(buf+0, kHeaderLen); set1(buf+1, kBytesPerEntry); set2BE(buf+2, (u2) threadCount); buf += kHeaderLen; pid_t pid = getpid(); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { unsigned long utime, stime; bool isDaemon; if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) { // failed; drop in empty values utime = stime = 0; } isDaemon = dvmGetFieldBoolean(thread->threadObj, gDvm.offJavaLangThread_daemon); set4BE(buf+0, thread->threadId); set1(buf+4, thread->status); set4BE(buf+5, thread->systemTid); set4BE(buf+9, utime); set4BE(buf+13, stime); set1(buf+17, isDaemon); buf += kBytesPerEntry; } dvmUnlockThreadList(); /* * Create a byte array to hold the data. */ ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT); if (arrayObj != NULL) memcpy(arrayObj->contents, tmpBuf, bufLen); return arrayObj; }
/* * Gather up the allocation data and copy it into a byte[]. * * Returns NULL on failure with an exception raised. */ ArrayObject* dvmDdmGetRecentAllocations(void) { u1* data; size_t len; if (!dvmGenerateTrackedAllocationReport(&data, &len)) { /* assume OOM */ dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native"); return NULL; } ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT); if (arrayObj != NULL) memcpy(arrayObj->contents, data, len); return arrayObj; }
/* * Allocate a new instance of the class String, performing first-use * initialization of the class if necessary. Upon success, the * returned value will have all its fields except hashCode already * filled in, including a reference to a newly-allocated char[] for * the contents, sized as given. Additionally, a reference to the * chars array is stored to the pChars pointer. Callers must * subsequently call dvmReleaseTrackedAlloc() on the result pointer. * This function returns NULL on failure. */ static StringObject* makeStringObject(u4 charsLength, ArrayObject** pChars) { /* * The String class should have already gotten found (but not * necessarily initialized) before making it here. We assert it * explicitly, since historically speaking, we have had bugs with * regard to when the class String gets set up. The assert helps * make any regressions easier to diagnose. */ assert(gDvm.classJavaLangString != NULL); if (!dvmIsClassInitialized(gDvm.classJavaLangString)) { /* Perform first-time use initialization of the class. */ if (!dvmInitClass(gDvm.classJavaLangString)) { LOGE("FATAL: Could not initialize class String"); dvmAbort(); } } Object* result = dvmAllocObject(gDvm.classJavaLangString, ALLOC_DEFAULT); if (result == NULL) { return NULL; } ArrayObject* chars = dvmAllocPrimitiveArray('C', charsLength, ALLOC_DEFAULT); if (chars == NULL) { dvmReleaseTrackedAlloc(result, NULL); return NULL; } dvmSetFieldInt(result, STRING_FIELDOFF_COUNT, charsLength); dvmSetFieldObject(result, STRING_FIELDOFF_VALUE, (Object*) chars); dvmReleaseTrackedAlloc((Object*) chars, NULL); /* Leave offset and hashCode set to zero. */ *pChars = chars; return (StringObject*) result; }
/* * "buf" contains a full JDWP packet, possibly with multiple chunks. We * need to process each, accumulate the replies, and ship the whole thing * back. * * Returns "true" if we have a reply. The reply buffer is newly allocated, * and includes the chunk type/length, followed by the data. * * TODO: we currently assume that the request and reply include a single * chunk. If this becomes inconvenient we will need to adapt. */ bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf, int* pReplyLen) { Thread* self = dvmThreadSelf(); const int kChunkHdrLen = 8; ArrayObject* dataArray = NULL; bool result = false; assert(dataLen >= 0); /* * Prep DdmServer. We could throw this in gDvm. */ ClassObject* ddmServerClass; Method* dispatch; ddmServerClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL); if (ddmServerClass == NULL) { LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n"); goto bail; } dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch", "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;"); if (dispatch == NULL) { LOGW("Unable to find DdmServer.dispatch\n"); goto bail; } /* * Prep Chunk. */ int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff; ClassObject* chunkClass; chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL); if (chunkClass == NULL) { LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n"); goto bail; } chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I"); chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B"); chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I"); chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I"); if (chunkTypeOff < 0 || chunkDataOff < 0 || chunkOffsetOff < 0 || chunkLengthOff < 0) { LOGW("Unable to find all chunk fields\n"); goto bail; } /* * The chunk handlers are written in the Java programming language, so * we need to convert the buffer to a byte array. */ dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT); if (dataArray == NULL) { LOGW("array alloc failed (%d)\n", dataLen); dvmClearException(self); goto bail; } memcpy(dataArray->contents, buf, dataLen); /* * Run through and find all chunks. [Currently just find the first.] */ unsigned int offset, length, type; type = get4BE((u1*)dataArray->contents + 0); length = get4BE((u1*)dataArray->contents + 4); offset = kChunkHdrLen; if (offset+length > (unsigned int) dataLen) { LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen); goto bail; } /* * Call the handler. */ JValue callRes; dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset, length); if (dvmCheckException(self)) { LOGI("Exception thrown by dispatcher for 0x%08x\n", type); dvmLogExceptionStackTrace(); dvmClearException(self); goto bail; } Object* chunk; ArrayObject* replyData; chunk = (Object*) callRes.l; if (chunk == NULL) goto bail; /* * Pull the pieces out of the chunk. We copy the results into a * newly-allocated buffer that the caller can free. We don't want to * continue using the Chunk object because nothing has a reference to it. * (If we do an alloc in here, we need to dvmAddTrackedAlloc it.) * * We could avoid this by returning type/data/offset/length and having * the caller be aware of the object lifetime issues, but that * integrates the JDWP code more tightly into the VM, and doesn't work * if we have responses for multiple chunks. * * So we're pretty much stuck with copying data around multiple times. */ type = dvmGetFieldInt(chunk, chunkTypeOff); replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff); offset = dvmGetFieldInt(chunk, chunkOffsetOff); length = dvmGetFieldInt(chunk, chunkLengthOff); LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n", type, replyData, offset, length); if (length == 0 || replyData == NULL) goto bail; if (offset + length > replyData->length) { LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n", offset, length, replyData->length); goto bail; } u1* reply; reply = (u1*) malloc(length + kChunkHdrLen); if (reply == NULL) { LOGW("malloc %d failed\n", length+kChunkHdrLen); goto bail; } set4BE(reply + 0, type); set4BE(reply + 4, length); memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length); *pReplyBuf = reply; *pReplyLen = length + kChunkHdrLen; result = true; LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n", (char*) reply, reply, length); bail: dvmReleaseTrackedAlloc((Object*) dataArray, NULL); return result; }
/* * We have to carry the exception's stack trace around, but in many cases * it will never be examined. It makes sense to keep it in a compact, * VM-specific object, rather than an array of Objects with strings. * * Pass in the thread whose stack we're interested in. If "thread" is * not self, the thread must be suspended. This implies that the thread * list lock is held, which means we can't allocate objects or we risk * jamming the GC. So, we allow this function to return different formats. * (This shouldn't be called directly -- see the inline functions in the * header file.) * * If "wantObject" is true, this returns a newly-allocated Object, which is * presently an array of integers, but could become something else in the * future. If "wantObject" is false, return plain malloc data. * * NOTE: if we support class unloading, we will need to scan the class * object references out of these arrays. */ void* dvmFillInStackTraceInternal(Thread* thread, bool wantObject, int* pCount) { ArrayObject* stackData = NULL; int* simpleData = NULL; void* fp; void* startFp; int stackDepth; int* intPtr; if (pCount != NULL) *pCount = 0; fp = thread->curFrame; assert(thread == dvmThreadSelf() || dvmIsSuspended(thread)); /* * We're looking at a stack frame for code running below a Throwable * constructor. We want to remove the Throwable methods and the * superclass initializations so the user doesn't see them when they * read the stack dump. * * TODO: this just scrapes off the top layers of Throwable. Might not do * the right thing if we create an exception object or cause a VM * exception while in a Throwable method. */ while (fp != NULL) { const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); const Method* method = saveArea->method; if (dvmIsBreakFrame(fp)) break; if (!dvmInstanceof(method->clazz, gDvm.classJavaLangThrowable)) break; //LOGD("EXCEP: ignoring %s.%s\n", // method->clazz->descriptor, method->name); fp = saveArea->prevFrame; } startFp = fp; /* * Compute the stack depth. */ stackDepth = 0; while (fp != NULL) { const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); if (!dvmIsBreakFrame(fp)) stackDepth++; assert(fp != saveArea->prevFrame); fp = saveArea->prevFrame; } //LOGD("EXCEP: stack depth is %d\n", stackDepth); if (!stackDepth) goto bail; /* * We need to store a pointer to the Method and the program counter. * We have 4-byte pointers, so we use '[I'. */ if (wantObject) { assert(sizeof(Method*) == 4); stackData = dvmAllocPrimitiveArray('I', stackDepth*2, ALLOC_DEFAULT); if (stackData == NULL) { assert(dvmCheckException(dvmThreadSelf())); goto bail; } intPtr = (int*) stackData->contents; } else { /* array of ints; first entry is stack depth */ assert(sizeof(Method*) == sizeof(int)); simpleData = (int*) malloc(sizeof(int) * stackDepth*2); if (simpleData == NULL) goto bail; assert(pCount != NULL); intPtr = simpleData; } if (pCount != NULL) *pCount = stackDepth; fp = startFp; while (fp != NULL) { const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); const Method* method = saveArea->method; if (!dvmIsBreakFrame(fp)) { //LOGD("EXCEP keeping %s.%s\n", method->clazz->descriptor, // method->name); *intPtr++ = (int) method; if (dvmIsNativeMethod(method)) { *intPtr++ = 0; /* no saved PC for native methods */ } else { assert(saveArea->xtra.currentPc >= method->insns && saveArea->xtra.currentPc < method->insns + dvmGetMethodInsnsSize(method)); *intPtr++ = (int) (saveArea->xtra.currentPc - method->insns); } stackDepth--; // for verification } assert(fp != saveArea->prevFrame); fp = saveArea->prevFrame; } assert(stackDepth == 0); bail: if (wantObject) { dvmReleaseTrackedAlloc((Object*) stackData, dvmThreadSelf()); return stackData; } else { return simpleData; } }