• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 /*
18  * Handle Dalvik Debug Monitor requests and events.
19  *
20  * Remember that all DDM traffic is big-endian since it travels over the
21  * JDWP connection.
22  */
23 #include "Dalvik.h"
24 
25 #include <fcntl.h>
26 #include <errno.h>
27 
28 /*
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
31  * back.
32  *
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.
35  *
36  * TODO: we currently assume that the request and reply include a single
37  * chunk.  If this becomes inconvenient we will need to adapt.
38  */
dvmDdmHandlePacket(const u1 * buf,int dataLen,u1 ** pReplyBuf,int * pReplyLen)39 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
40     int* pReplyLen)
41 {
42     Thread* self = dvmThreadSelf();
43     const int kChunkHdrLen = 8;
44     ArrayObject* dataArray = NULL;
45     Object* chunk = NULL;
46     bool result = false;
47 
48     assert(dataLen >= 0);
49 
50     if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) {
51         if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) {
52             dvmLogExceptionStackTrace();
53             dvmClearException(self);
54             goto bail;
55         }
56     }
57 
58     /*
59      * The chunk handlers are written in the Java programming language, so
60      * we need to convert the buffer to a byte array.
61      */
62     dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
63     if (dataArray == NULL) {
64         ALOGW("array alloc failed (%d)", dataLen);
65         dvmClearException(self);
66         goto bail;
67     }
68     memcpy(dataArray->contents, buf, dataLen);
69 
70     /*
71      * Run through and find all chunks.  [Currently just find the first.]
72      */
73     unsigned int offset, length, type;
74     type = get4BE((u1*)dataArray->contents + 0);
75     length = get4BE((u1*)dataArray->contents + 4);
76     offset = kChunkHdrLen;
77     if (offset+length > (unsigned int) dataLen) {
78         ALOGW("WARNING: bad chunk found (len=%u pktLen=%d)", length, dataLen);
79         goto bail;
80     }
81 
82     /*
83      * Call the handler.
84      */
85     JValue callRes;
86     dvmCallMethod(self, gDvm.methDalvikDdmcServer_dispatch, NULL, &callRes,
87         type, dataArray, offset, length);
88     if (dvmCheckException(self)) {
89         ALOGI("Exception thrown by dispatcher for 0x%08x", type);
90         dvmLogExceptionStackTrace();
91         dvmClearException(self);
92         goto bail;
93     }
94 
95     ArrayObject* replyData;
96     chunk = (Object*) callRes.l;
97     if (chunk == NULL)
98         goto bail;
99 
100     /* not strictly necessary -- we don't alloc from managed heap here */
101     dvmAddTrackedAlloc(chunk, self);
102 
103     /*
104      * Pull the pieces out of the chunk.  We copy the results into a
105      * newly-allocated buffer that the caller can free.  We don't want to
106      * continue using the Chunk object because nothing has a reference to it.
107      *
108      * We could avoid this by returning type/data/offset/length and having
109      * the caller be aware of the object lifetime issues, but that
110      * integrates the JDWP code more tightly into the VM, and doesn't work
111      * if we have responses for multiple chunks.
112      *
113      * So we're pretty much stuck with copying data around multiple times.
114      */
115     type = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_type);
116     replyData =
117         (ArrayObject*) dvmGetFieldObject(chunk, gDvm.offDalvikDdmcChunk_data);
118     offset = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_offset);
119     length = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_length);
120 
121     ALOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d",
122         type, replyData, offset, length);
123 
124     if (length == 0 || replyData == NULL)
125         goto bail;
126     if (offset + length > replyData->length) {
127         ALOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d",
128             offset, length, replyData->length);
129         goto bail;
130     }
131 
132     u1* reply;
133     reply = (u1*) malloc(length + kChunkHdrLen);
134     if (reply == NULL) {
135         ALOGW("malloc %d failed", length+kChunkHdrLen);
136         goto bail;
137     }
138     set4BE(reply + 0, type);
139     set4BE(reply + 4, length);
140     memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
141 
142     *pReplyBuf = reply;
143     *pReplyLen = length + kChunkHdrLen;
144     result = true;
145 
146     ALOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d",
147         (char*) reply, reply, length);
148 
149 bail:
150     dvmReleaseTrackedAlloc((Object*) dataArray, self);
151     dvmReleaseTrackedAlloc(chunk, self);
152     return result;
153 }
154 
155 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
156 #define CONNECTED       1
157 #define DISCONNECTED    2
158 
159 /*
160  * Broadcast an event to all handlers.
161  */
broadcast(int event)162 static void broadcast(int event)
163 {
164     Thread* self = dvmThreadSelf();
165 
166     if (self->status != THREAD_RUNNING) {
167         ALOGE("ERROR: DDM broadcast with thread status=%d", self->status);
168         /* try anyway? */
169     }
170 
171     if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) {
172         if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) {
173             dvmLogExceptionStackTrace();
174             dvmClearException(self);
175             return;
176         }
177     }
178 
179     JValue unused;
180     dvmCallMethod(self, gDvm.methDalvikDdmcServer_broadcast, NULL, &unused,
181         event);
182     if (dvmCheckException(self)) {
183         ALOGI("Exception thrown by broadcast(%d)", event);
184         dvmLogExceptionStackTrace();
185         dvmClearException(self);
186         return;
187     }
188 }
189 
190 /*
191  * First DDM packet has arrived over JDWP.  Notify the press.
192  *
193  * We can do some initialization here too.
194  */
dvmDdmConnected()195 void dvmDdmConnected()
196 {
197     // TODO: any init
198 
199     ALOGV("Broadcasting DDM connect");
200     broadcast(CONNECTED);
201 }
202 
203 /*
204  * JDWP connection has dropped.
205  *
206  * Do some cleanup.
207  */
dvmDdmDisconnected()208 void dvmDdmDisconnected()
209 {
210     ALOGV("Broadcasting DDM disconnect");
211     broadcast(DISCONNECTED);
212 
213     gDvm.ddmThreadNotification = false;
214 }
215 
216 
217 /*
218  * Turn thread notification on or off.
219  */
dvmDdmSetThreadNotification(bool enable)220 void dvmDdmSetThreadNotification(bool enable)
221 {
222     /*
223      * We lock the thread list to avoid sending duplicate events or missing
224      * a thread change.  We should be okay holding this lock while sending
225      * the messages out.  (We have to hold it while accessing a live thread.)
226      */
227     dvmLockThreadList(NULL);
228     gDvm.ddmThreadNotification = enable;
229 
230     if (enable) {
231         Thread* thread;
232         for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
233             //ALOGW("notify %d", thread->threadId);
234             dvmDdmSendThreadNotification(thread, true);
235         }
236     }
237 
238     dvmUnlockThreadList();
239 }
240 
241 /*
242  * Send a notification when a thread starts or stops.
243  *
244  * Because we broadcast the full set of threads when the notifications are
245  * first enabled, it's possible for "thread" to be actively executing.
246  */
dvmDdmSendThreadNotification(Thread * thread,bool started)247 void dvmDdmSendThreadNotification(Thread* thread, bool started)
248 {
249     if (!gDvm.ddmThreadNotification) {
250         return;
251     }
252 
253     StringObject* nameObj = NULL;
254     Object* threadObj = thread->threadObj;
255 
256     if (threadObj != NULL) {
257         nameObj = (StringObject*)
258             dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
259     }
260 
261     int type, len;
262     u1 buf[256];
263 
264     if (started) {
265         const u2* chars;
266         u2* outChars;
267         size_t stringLen;
268 
269         type = CHUNK_TYPE("THCR");
270 
271         if (nameObj != NULL) {
272             stringLen = nameObj->length();
273             chars = nameObj->chars();
274         } else {
275             stringLen = 0;
276             chars = NULL;
277         }
278 
279         /* leave room for the two integer fields */
280         if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2) {
281             stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
282         }
283         len = stringLen*2 + sizeof(u4)*2;
284 
285         set4BE(&buf[0x00], thread->threadId);
286         set4BE(&buf[0x04], stringLen);
287 
288         /* copy the UTF-16 string, transforming to big-endian */
289         outChars = (u2*)(void*)&buf[0x08];
290         while (stringLen--) {
291             set2BE((u1*) (outChars++), *chars++);
292         }
293     } else {
294         type = CHUNK_TYPE("THDE");
295 
296         len = 4;
297 
298         set4BE(&buf[0x00], thread->threadId);
299     }
300 
301     dvmDbgDdmSendChunk(type, len, buf);
302 }
303 
304 /*
305  * Send a notification when a thread's name changes.
306  */
dvmDdmSendThreadNameChange(int threadId,StringObject * newName)307 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
308 {
309     if (!gDvm.ddmThreadNotification) {
310         return;
311     }
312 
313     size_t stringLen = newName->length();
314     const u2* chars = newName->chars();
315 
316     /*
317      * Output format:
318      *  (4b) thread ID
319      *  (4b) stringLen
320      *  (xb) string chars
321      */
322     int bufLen = 4 + 4 + (stringLen * 2);
323     u1 buf[bufLen];
324 
325     set4BE(&buf[0x00], threadId);
326     set4BE(&buf[0x04], stringLen);
327     u2* outChars = (u2*)(void*)&buf[0x08];
328     while (stringLen--) {
329         set2BE((u1*) (outChars++), *chars++);
330     }
331 
332     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
333 }
334 
335 /*
336  * Generate the contents of a THST chunk.  The data encompasses all known
337  * threads.
338  *
339  * Response has:
340  *  (1b) header len
341  *  (1b) bytes per entry
342  *  (2b) thread count
343  * Then, for each thread:
344  *  (4b) threadId
345  *  (1b) thread status
346  *  (4b) tid
347  *  (4b) utime
348  *  (4b) stime
349  *  (1b) is daemon?
350  *
351  * The length fields exist in anticipation of adding additional fields
352  * without wanting to break ddms or bump the full protocol version.  I don't
353  * think it warrants full versioning.  They might be extraneous and could
354  * be removed from a future version.
355  *
356  * Returns a new byte[] with the data inside, or NULL on failure.  The
357  * caller must call dvmReleaseTrackedAlloc() on the array.
358  */
dvmDdmGenerateThreadStats()359 ArrayObject* dvmDdmGenerateThreadStats()
360 {
361     const int kHeaderLen = 4;
362     const int kBytesPerEntry = 18;
363 
364     dvmLockThreadList(NULL);
365 
366     Thread* thread;
367     int threadCount = 0;
368     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
369         threadCount++;
370 
371     /*
372      * Create a temporary buffer.  We can't perform heap allocation with
373      * the thread list lock held (could cause a GC).  The output is small
374      * enough to sit on the stack.
375      */
376     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
377     u1 tmpBuf[bufLen];
378     u1* buf = tmpBuf;
379 
380     set1(buf+0, kHeaderLen);
381     set1(buf+1, kBytesPerEntry);
382     set2BE(buf+2, (u2) threadCount);
383     buf += kHeaderLen;
384 
385     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
386         bool isDaemon = false;
387 
388         ProcStatData procStatData;
389         if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
390             /* failed; show zero */
391             memset(&procStatData, 0, sizeof(procStatData));
392         }
393 
394         Object* threadObj = thread->threadObj;
395         if (threadObj != NULL) {
396             isDaemon = dvmGetFieldBoolean(threadObj,
397                             gDvm.offJavaLangThread_daemon);
398         }
399 
400         set4BE(buf+0, thread->threadId);
401         set1(buf+4, thread->status);
402         set4BE(buf+5, thread->systemTid);
403         set4BE(buf+9, procStatData.utime);
404         set4BE(buf+13, procStatData.stime);
405         set1(buf+17, isDaemon);
406 
407         buf += kBytesPerEntry;
408     }
409     dvmUnlockThreadList();
410 
411 
412     /*
413      * Create a byte array to hold the data.
414      */
415     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
416     if (arrayObj != NULL)
417         memcpy(arrayObj->contents, tmpBuf, bufLen);
418     return arrayObj;
419 }
420 
421 
422 /*
423  * Find the specified thread and return its stack trace as an array of
424  * StackTraceElement objects.
425  */
dvmDdmGetStackTraceById(u4 threadId)426 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
427 {
428     Thread* self = dvmThreadSelf();
429     Thread* thread;
430     int* traceBuf;
431 
432     dvmLockThreadList(self);
433 
434     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
435         if (thread->threadId == threadId)
436             break;
437     }
438     if (thread == NULL) {
439         ALOGI("dvmDdmGetStackTraceById: threadid=%d not found", threadId);
440         dvmUnlockThreadList();
441         return NULL;
442     }
443 
444     /*
445      * Suspend the thread, pull out the stack trace, then resume the thread
446      * and release the thread list lock.  If we're being asked to examine
447      * our own stack trace, skip the suspend/resume.
448      */
449     size_t stackDepth;
450     if (thread != self)
451         dvmSuspendThread(thread);
452     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
453     if (thread != self)
454         dvmResumeThread(thread);
455     dvmUnlockThreadList();
456 
457     /*
458      * Convert the raw buffer into an array of StackTraceElement.
459      */
460     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
461     free(traceBuf);
462     return trace;
463 }
464 
465 /*
466  * Gather up the allocation data and copy it into a byte[].
467  *
468  * Returns NULL on failure with an exception raised.
469  */
dvmDdmGetRecentAllocations()470 ArrayObject* dvmDdmGetRecentAllocations()
471 {
472     u1* data;
473     size_t len;
474 
475     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
476         /* assume OOM */
477         dvmThrowOutOfMemoryError("recent alloc native");
478         return NULL;
479     }
480 
481     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
482     if (arrayObj != NULL)
483         memcpy(arrayObj->contents, data, len);
484     return arrayObj;
485 }
486