2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * Handle Dalvik Debug Monitor requests and events.
20 * Remember that all DDM traffic is big-endian since it travels over the
29 * "buf" contains a full JDWP packet, possibly with multiple chunks. We
30 * need to process each, accumulate the replies, and ship the whole thing
33 * Returns "true" if we have a reply. The reply buffer is newly allocated,
34 * and includes the chunk type/length, followed by the data.
36 * TODO: we currently assume that the request and reply include a single
37 * chunk. If this becomes inconvenient we will need to adapt.
39 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
42 Thread* self = dvmThreadSelf();
43 const int kChunkHdrLen = 8;
44 ArrayObject* dataArray = NULL;
51 * Prep DdmServer. We could throw this in gDvm.
53 ClassObject* ddmServerClass;
57 dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
58 if (ddmServerClass == NULL) {
59 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
62 dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch",
63 "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
64 if (dispatch == NULL) {
65 LOGW("Unable to find DdmServer.dispatch\n");
72 int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff;
73 ClassObject* chunkClass;
74 chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL);
75 if (chunkClass == NULL) {
76 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n");
79 chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I");
80 chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B");
81 chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I");
82 chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I");
83 if (chunkTypeOff < 0 || chunkDataOff < 0 ||
84 chunkOffsetOff < 0 || chunkLengthOff < 0)
86 LOGW("Unable to find all chunk fields\n");
91 * The chunk handlers are written in the Java programming language, so
92 * we need to convert the buffer to a byte array.
94 dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
95 if (dataArray == NULL) {
96 LOGW("array alloc failed (%d)\n", dataLen);
97 dvmClearException(self);
100 memcpy(dataArray->contents, buf, dataLen);
103 * Run through and find all chunks. [Currently just find the first.]
105 unsigned int offset, length, type;
106 type = get4BE((u1*)dataArray->contents + 0);
107 length = get4BE((u1*)dataArray->contents + 4);
108 offset = kChunkHdrLen;
109 if (offset+length > (unsigned int) dataLen) {
110 LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen);
118 dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset,
120 if (dvmCheckException(self)) {
121 LOGI("Exception thrown by dispatcher for 0x%08x\n", type);
122 dvmLogExceptionStackTrace();
123 dvmClearException(self);
127 ArrayObject* replyData;
128 chunk = (Object*) callRes.l;
132 /* not strictly necessary -- we don't alloc from managed heap here */
133 dvmAddTrackedAlloc(chunk, self);
136 * Pull the pieces out of the chunk. We copy the results into a
137 * newly-allocated buffer that the caller can free. We don't want to
138 * continue using the Chunk object because nothing has a reference to it.
140 * We could avoid this by returning type/data/offset/length and having
141 * the caller be aware of the object lifetime issues, but that
142 * integrates the JDWP code more tightly into the VM, and doesn't work
143 * if we have responses for multiple chunks.
145 * So we're pretty much stuck with copying data around multiple times.
147 type = dvmGetFieldInt(chunk, chunkTypeOff);
148 replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff);
149 offset = dvmGetFieldInt(chunk, chunkOffsetOff);
150 length = dvmGetFieldInt(chunk, chunkLengthOff);
152 LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
153 type, replyData, offset, length);
155 if (length == 0 || replyData == NULL)
157 if (offset + length > replyData->length) {
158 LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n",
159 offset, length, replyData->length);
164 reply = (u1*) malloc(length + kChunkHdrLen);
166 LOGW("malloc %d failed\n", length+kChunkHdrLen);
169 set4BE(reply + 0, type);
170 set4BE(reply + 4, length);
171 memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
174 *pReplyLen = length + kChunkHdrLen;
177 LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
178 (char*) reply, reply, length);
181 dvmReleaseTrackedAlloc((Object*) dataArray, self);
182 dvmReleaseTrackedAlloc(chunk, self);
186 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
188 #define DISCONNECTED 2
191 * Broadcast an event to all handlers.
193 static void broadcast(int event)
195 ClassObject* ddmServerClass;
199 dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
200 if (ddmServerClass == NULL) {
201 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
204 bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V");
206 LOGW("Unable to find DdmServer.broadcast\n");
210 Thread* self = dvmThreadSelf();
212 if (self->status != THREAD_RUNNING) {
213 LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status);
218 dvmCallMethod(self, bcast, NULL, &unused, event);
219 if (dvmCheckException(self)) {
220 LOGI("Exception thrown by broadcast(%d)\n", event);
221 dvmLogExceptionStackTrace();
222 dvmClearException(self);
231 * First DDM packet has arrived over JDWP. Notify the press.
233 * We can do some initialization here too.
235 void dvmDdmConnected(void)
239 LOGV("Broadcasting DDM connect\n");
240 broadcast(CONNECTED);
244 * JDWP connection has dropped.
248 void dvmDdmDisconnected(void)
250 LOGV("Broadcasting DDM disconnect\n");
251 broadcast(DISCONNECTED);
253 gDvm.ddmThreadNotification = false;
258 * Turn thread notification on or off.
260 void dvmDdmSetThreadNotification(bool enable)
263 * We lock the thread list to avoid sending duplicate events or missing
264 * a thread change. We should be okay holding this lock while sending
265 * the messages out. (We have to hold it while accessing a live thread.)
267 dvmLockThreadList(NULL);
268 gDvm.ddmThreadNotification = enable;
272 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
273 //LOGW("notify %d\n", thread->threadId);
274 dvmDdmSendThreadNotification(thread, true);
278 dvmUnlockThreadList();
282 * Send a notification when a thread starts or stops.
284 * Because we broadcast the full set of threads when the notifications are
285 * first enabled, it's possible for "thread" to be actively executing.
287 void dvmDdmSendThreadNotification(Thread* thread, bool started)
289 if (!gDvm.ddmThreadNotification)
292 StringObject* nameObj = NULL;
293 Object* threadObj = thread->threadObj;
295 if (threadObj != NULL) {
296 nameObj = (StringObject*)
297 dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
308 type = CHUNK_TYPE("THCR");
310 if (nameObj != NULL) {
311 stringLen = dvmStringLen(nameObj);
312 chars = dvmStringChars(nameObj);
318 /* leave room for the two integer fields */
319 if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
320 stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
321 len = stringLen*2 + sizeof(u4)*2;
323 set4BE(&buf[0x00], thread->threadId);
324 set4BE(&buf[0x04], stringLen);
326 /* copy the UTF-16 string, transforming to big-endian */
327 outChars = (u2*) &buf[0x08];
329 set2BE((u1*) (outChars++), *chars++);
331 type = CHUNK_TYPE("THDE");
335 set4BE(&buf[0x00], thread->threadId);
338 dvmDbgDdmSendChunk(type, len, buf);
342 * Send a notification when a thread's name changes.
344 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
346 if (!gDvm.ddmThreadNotification)
349 size_t stringLen = dvmStringLen(newName);
350 const u2* chars = dvmStringChars(newName);
358 int bufLen = 4 + 4 + (stringLen * 2);
361 set4BE(&buf[0x00], threadId);
362 set4BE(&buf[0x04], stringLen);
363 u2* outChars = (u2*) &buf[0x08];
365 set2BE((u1*) (outChars++), *chars++);
367 dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
371 * Generate the contents of a THST chunk. The data encompasses all known
376 * (1b) bytes per entry
378 * Then, for each thread:
386 * The length fields exist in anticipation of adding additional fields
387 * without wanting to break ddms or bump the full protocol version. I don't
388 * think it warrants full versioning. They might be extraneous and could
389 * be removed from a future version.
391 * Returns a new byte[] with the data inside, or NULL on failure. The
392 * caller must call dvmReleaseTrackedAlloc() on the array.
394 ArrayObject* dvmDdmGenerateThreadStats(void)
396 const int kHeaderLen = 4;
397 const int kBytesPerEntry = 18;
399 dvmLockThreadList(NULL);
403 for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
407 * Create a temporary buffer. We can't perform heap allocation with
408 * the thread list lock held (could cause a GC). The output is small
409 * enough to sit on the stack.
411 int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
415 set1(buf+0, kHeaderLen);
416 set1(buf+1, kBytesPerEntry);
417 set2BE(buf+2, (u2) threadCount);
420 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
421 bool isDaemon = false;
423 ProcStatData procStatData;
424 if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
425 /* failed; show zero */
426 memset(&procStatData, 0, sizeof(procStatData));
429 Object* threadObj = thread->threadObj;
430 if (threadObj != NULL) {
431 isDaemon = dvmGetFieldBoolean(threadObj,
432 gDvm.offJavaLangThread_daemon);
435 set4BE(buf+0, thread->threadId);
436 set1(buf+4, thread->status);
437 set4BE(buf+5, thread->systemTid);
438 set4BE(buf+9, procStatData.utime);
439 set4BE(buf+13, procStatData.stime);
440 set1(buf+17, isDaemon);
442 buf += kBytesPerEntry;
444 dvmUnlockThreadList();
448 * Create a byte array to hold the data.
450 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
451 if (arrayObj != NULL)
452 memcpy(arrayObj->contents, tmpBuf, bufLen);
458 * Find the specified thread and return its stack trace as an array of
459 * StackTraceElement objects.
461 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
463 Thread* self = dvmThreadSelf();
467 dvmLockThreadList(self);
469 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
470 if (thread->threadId == threadId)
473 if (thread == NULL) {
474 LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
475 dvmUnlockThreadList();
480 * Suspend the thread, pull out the stack trace, then resume the thread
481 * and release the thread list lock. If we're being asked to examine
482 * our own stack trace, skip the suspend/resume.
486 dvmSuspendThread(thread);
487 traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
489 dvmResumeThread(thread);
490 dvmUnlockThreadList();
493 * Convert the raw buffer into an array of StackTraceElement.
495 ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
501 * Gather up the allocation data and copy it into a byte[].
503 * Returns NULL on failure with an exception raised.
505 ArrayObject* dvmDdmGetRecentAllocations(void)
510 if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
512 dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
516 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
517 if (arrayObj != NULL)
518 memcpy(arrayObj->contents, data, len);