| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Handle Dalvik Debug Monitor requests and events. |
| * |
| * Remember that all DDM traffic is big-endian since it travels over the |
| * JDWP connection. |
| */ |
| #include "Dalvik.h" |
| |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| /* |
| * "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; |
| Object* chunk = NULL; |
| bool result = false; |
| |
| assert(dataLen >= 0); |
| |
| if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) { |
| if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) { |
| dvmLogExceptionStackTrace(); |
| dvmClearException(self); |
| 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) { |
| ALOGW("array alloc failed (%d)", 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) { |
| ALOGW("WARNING: bad chunk found (len=%u pktLen=%d)", length, dataLen); |
| goto bail; |
| } |
| |
| /* |
| * Call the handler. |
| */ |
| JValue callRes; |
| dvmCallMethod(self, gDvm.methDalvikDdmcServer_dispatch, NULL, &callRes, |
| type, dataArray, offset, length); |
| if (dvmCheckException(self)) { |
| ALOGI("Exception thrown by dispatcher for 0x%08x", type); |
| dvmLogExceptionStackTrace(); |
| dvmClearException(self); |
| goto bail; |
| } |
| |
| ArrayObject* replyData; |
| chunk = (Object*) callRes.l; |
| if (chunk == NULL) |
| goto bail; |
| |
| /* not strictly necessary -- we don't alloc from managed heap here */ |
| dvmAddTrackedAlloc(chunk, self); |
| |
| /* |
| * 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. |
| * |
| * 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, gDvm.offDalvikDdmcChunk_type); |
| replyData = |
| (ArrayObject*) dvmGetFieldObject(chunk, gDvm.offDalvikDdmcChunk_data); |
| offset = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_offset); |
| length = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_length); |
| |
| ALOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d", |
| type, replyData, offset, length); |
| |
| if (length == 0 || replyData == NULL) |
| goto bail; |
| if (offset + length > replyData->length) { |
| ALOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d", |
| offset, length, replyData->length); |
| goto bail; |
| } |
| |
| u1* reply; |
| reply = (u1*) malloc(length + kChunkHdrLen); |
| if (reply == NULL) { |
| ALOGW("malloc %d failed", 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; |
| |
| ALOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d", |
| (char*) reply, reply, length); |
| |
| bail: |
| dvmReleaseTrackedAlloc((Object*) dataArray, self); |
| dvmReleaseTrackedAlloc(chunk, self); |
| return result; |
| } |
| |
| /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */ |
| #define CONNECTED 1 |
| #define DISCONNECTED 2 |
| |
| /* |
| * Broadcast an event to all handlers. |
| */ |
| static void broadcast(int event) |
| { |
| Thread* self = dvmThreadSelf(); |
| |
| if (self->status != THREAD_RUNNING) { |
| ALOGE("ERROR: DDM broadcast with thread status=%d", self->status); |
| /* try anyway? */ |
| } |
| |
| if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) { |
| if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) { |
| dvmLogExceptionStackTrace(); |
| dvmClearException(self); |
| return; |
| } |
| } |
| |
| JValue unused; |
| dvmCallMethod(self, gDvm.methDalvikDdmcServer_broadcast, NULL, &unused, |
| event); |
| if (dvmCheckException(self)) { |
| ALOGI("Exception thrown by broadcast(%d)", event); |
| dvmLogExceptionStackTrace(); |
| dvmClearException(self); |
| return; |
| } |
| } |
| |
| /* |
| * First DDM packet has arrived over JDWP. Notify the press. |
| * |
| * We can do some initialization here too. |
| */ |
| void dvmDdmConnected() |
| { |
| // TODO: any init |
| |
| ALOGV("Broadcasting DDM connect"); |
| broadcast(CONNECTED); |
| } |
| |
| /* |
| * JDWP connection has dropped. |
| * |
| * Do some cleanup. |
| */ |
| void dvmDdmDisconnected() |
| { |
| ALOGV("Broadcasting DDM disconnect"); |
| broadcast(DISCONNECTED); |
| |
| gDvm.ddmThreadNotification = false; |
| } |
| |
| |
| /* |
| * Turn thread notification on or off. |
| */ |
| void dvmDdmSetThreadNotification(bool enable) |
| { |
| /* |
| * We lock the thread list to avoid sending duplicate events or missing |
| * a thread change. We should be okay holding this lock while sending |
| * the messages out. (We have to hold it while accessing a live thread.) |
| */ |
| dvmLockThreadList(NULL); |
| gDvm.ddmThreadNotification = enable; |
| |
| if (enable) { |
| Thread* thread; |
| for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { |
| //ALOGW("notify %d", thread->threadId); |
| dvmDdmSendThreadNotification(thread, true); |
| } |
| } |
| |
| dvmUnlockThreadList(); |
| } |
| |
| /* |
| * Send a notification when a thread starts or stops. |
| * |
| * Because we broadcast the full set of threads when the notifications are |
| * first enabled, it's possible for "thread" to be actively executing. |
| */ |
| void dvmDdmSendThreadNotification(Thread* thread, bool started) |
| { |
| if (!gDvm.ddmThreadNotification) { |
| return; |
| } |
| |
| StringObject* nameObj = NULL; |
| Object* threadObj = thread->threadObj; |
| |
| if (threadObj != NULL) { |
| nameObj = (StringObject*) |
| dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name); |
| } |
| |
| int type, len; |
| u1 buf[256]; |
| |
| if (started) { |
| const u2* chars; |
| u2* outChars; |
| size_t stringLen; |
| |
| type = CHUNK_TYPE("THCR"); |
| |
| if (nameObj != NULL) { |
| stringLen = nameObj->length(); |
| chars = nameObj->chars(); |
| } else { |
| stringLen = 0; |
| chars = NULL; |
| } |
| |
| /* leave room for the two integer fields */ |
| if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2) { |
| stringLen = (sizeof(buf) - sizeof(u4)*2) / 2; |
| } |
| len = stringLen*2 + sizeof(u4)*2; |
| |
| set4BE(&buf[0x00], thread->threadId); |
| set4BE(&buf[0x04], stringLen); |
| |
| /* copy the UTF-16 string, transforming to big-endian */ |
| outChars = (u2*)(void*)&buf[0x08]; |
| while (stringLen--) { |
| set2BE((u1*) (outChars++), *chars++); |
| } |
| } else { |
| type = CHUNK_TYPE("THDE"); |
| |
| len = 4; |
| |
| set4BE(&buf[0x00], thread->threadId); |
| } |
| |
| dvmDbgDdmSendChunk(type, len, buf); |
| } |
| |
| /* |
| * Send a notification when a thread's name changes. |
| */ |
| void dvmDdmSendThreadNameChange(int threadId, StringObject* newName) |
| { |
| if (!gDvm.ddmThreadNotification) { |
| return; |
| } |
| |
| size_t stringLen = newName->length(); |
| const u2* chars = newName->chars(); |
| |
| /* |
| * Output format: |
| * (4b) thread ID |
| * (4b) stringLen |
| * (xb) string chars |
| */ |
| int bufLen = 4 + 4 + (stringLen * 2); |
| u1 buf[bufLen]; |
| |
| set4BE(&buf[0x00], threadId); |
| set4BE(&buf[0x04], stringLen); |
| u2* outChars = (u2*)(void*)&buf[0x08]; |
| while (stringLen--) { |
| set2BE((u1*) (outChars++), *chars++); |
| } |
| |
| dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf); |
| } |
| |
| /* |
| * 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() |
| { |
| 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; |
| |
| for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { |
| bool isDaemon = false; |
| |
| ProcStatData procStatData; |
| if (!dvmGetThreadStats(&procStatData, thread->systemTid)) { |
| /* failed; show zero */ |
| memset(&procStatData, 0, sizeof(procStatData)); |
| } |
| |
| Object* threadObj = thread->threadObj; |
| if (threadObj != NULL) { |
| isDaemon = dvmGetFieldBoolean(threadObj, |
| gDvm.offJavaLangThread_daemon); |
| } |
| |
| set4BE(buf+0, thread->threadId); |
| set1(buf+4, thread->status); |
| set4BE(buf+5, thread->systemTid); |
| set4BE(buf+9, procStatData.utime); |
| set4BE(buf+13, procStatData.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; |
| } |
| |
| |
| /* |
| * Find the specified thread and return its stack trace as an array of |
| * StackTraceElement objects. |
| */ |
| ArrayObject* dvmDdmGetStackTraceById(u4 threadId) |
| { |
| Thread* self = dvmThreadSelf(); |
| Thread* thread; |
| int* traceBuf; |
| |
| dvmLockThreadList(self); |
| |
| for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { |
| if (thread->threadId == threadId) |
| break; |
| } |
| if (thread == NULL) { |
| ALOGI("dvmDdmGetStackTraceById: threadid=%d not found", threadId); |
| dvmUnlockThreadList(); |
| return NULL; |
| } |
| |
| /* |
| * Suspend the thread, pull out the stack trace, then resume the thread |
| * and release the thread list lock. If we're being asked to examine |
| * our own stack trace, skip the suspend/resume. |
| */ |
| size_t stackDepth; |
| if (thread != self) |
| dvmSuspendThread(thread); |
| traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth); |
| if (thread != self) |
| dvmResumeThread(thread); |
| dvmUnlockThreadList(); |
| |
| /* |
| * Convert the raw buffer into an array of StackTraceElement. |
| */ |
| ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth); |
| free(traceBuf); |
| return trace; |
| } |
| |
| /* |
| * Gather up the allocation data and copy it into a byte[]. |
| * |
| * Returns NULL on failure with an exception raised. |
| */ |
| ArrayObject* dvmDdmGetRecentAllocations() |
| { |
| u1* data; |
| size_t len; |
| |
| if (!dvmGenerateTrackedAllocationReport(&data, &len)) { |
| /* assume OOM */ |
| dvmThrowOutOfMemoryError("recent alloc native"); |
| return NULL; |
| } |
| |
| ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT); |
| if (arrayObj != NULL) |
| memcpy(arrayObj->contents, data, len); |
| return arrayObj; |
| } |