• 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  * Handle Dalvik Debug Monitor requests and events.
18  *
19  * Remember that all DDM traffic is big-endian since it travels over the
20  * JDWP connection.
21  */
22 #include "Dalvik.h"
23 
24 #include <fcntl.h>
25 #include <errno.h>
26 
27 /*
28  * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
29  * need to process each, accumulate the replies, and ship the whole thing
30  * back.
31  *
32  * Returns "true" if we have a reply.  The reply buffer is newly allocated,
33  * and includes the chunk type/length, followed by the data.
34  *
35  * TODO: we currently assume that the request and reply include a single
36  * chunk.  If this becomes inconvenient we will need to adapt.
37  */
dvmDdmHandlePacket(const u1 * buf,int dataLen,u1 ** pReplyBuf,int * pReplyLen)38 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
39     int* pReplyLen)
40 {
41     Thread* self = dvmThreadSelf();
42     const int kChunkHdrLen = 8;
43     ArrayObject* dataArray = NULL;
44     bool result = false;
45 
46     assert(dataLen >= 0);
47 
48     /*
49      * Prep DdmServer.  We could throw this in gDvm.
50      */
51     ClassObject* ddmServerClass;
52     Method* dispatch;
53 
54     ddmServerClass =
55         dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
56     if (ddmServerClass == NULL) {
57         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
58         goto bail;
59     }
60     dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch",
61                     "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
62     if (dispatch == NULL) {
63         LOGW("Unable to find DdmServer.dispatch\n");
64         goto bail;
65     }
66 
67     /*
68      * Prep Chunk.
69      */
70     int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff;
71     ClassObject* chunkClass;
72     chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL);
73     if (chunkClass == NULL) {
74         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n");
75         goto bail;
76     }
77     chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I");
78     chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B");
79     chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I");
80     chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I");
81     if (chunkTypeOff < 0 || chunkDataOff < 0 ||
82         chunkOffsetOff < 0 || chunkLengthOff < 0)
83     {
84         LOGW("Unable to find all chunk fields\n");
85         goto bail;
86     }
87 
88     /*
89      * The chunk handlers are written in the Java programming language, so
90      * we need to convert the buffer to a byte array.
91      */
92     dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
93     if (dataArray == NULL) {
94         LOGW("array alloc failed (%d)\n", dataLen);
95         dvmClearException(self);
96         goto bail;
97     }
98     memcpy(dataArray->contents, buf, dataLen);
99 
100     /*
101      * Run through and find all chunks.  [Currently just find the first.]
102      */
103     unsigned int offset, length, type;
104     type = get4BE((u1*)dataArray->contents + 0);
105     length = get4BE((u1*)dataArray->contents + 4);
106     offset = kChunkHdrLen;
107     if (offset+length > (unsigned int) dataLen) {
108         LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen);
109         goto bail;
110     }
111 
112     /*
113      * Call the handler.
114      */
115     JValue callRes;
116     dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset,
117         length);
118     if (dvmCheckException(self)) {
119         LOGI("Exception thrown by dispatcher for 0x%08x\n", type);
120         dvmLogExceptionStackTrace();
121         dvmClearException(self);
122         goto bail;
123     }
124 
125     Object* chunk;
126     ArrayObject* replyData;
127     chunk = (Object*) callRes.l;
128     if (chunk == NULL)
129         goto bail;
130 
131     /*
132      * Pull the pieces out of the chunk.  We copy the results into a
133      * newly-allocated buffer that the caller can free.  We don't want to
134      * continue using the Chunk object because nothing has a reference to it.
135      * (If we do an alloc in here, we need to dvmAddTrackedAlloc it.)
136      *
137      * We could avoid this by returning type/data/offset/length and having
138      * the caller be aware of the object lifetime issues, but that
139      * integrates the JDWP code more tightly into the VM, and doesn't work
140      * if we have responses for multiple chunks.
141      *
142      * So we're pretty much stuck with copying data around multiple times.
143      */
144     type = dvmGetFieldInt(chunk, chunkTypeOff);
145     replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff);
146     offset = dvmGetFieldInt(chunk, chunkOffsetOff);
147     length = dvmGetFieldInt(chunk, chunkLengthOff);
148 
149     LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
150         type, replyData, offset, length);
151 
152     if (length == 0 || replyData == NULL)
153         goto bail;
154     if (offset + length > replyData->length) {
155         LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n",
156             offset, length, replyData->length);
157         goto bail;
158     }
159 
160     u1* reply;
161     reply = (u1*) malloc(length + kChunkHdrLen);
162     if (reply == NULL) {
163         LOGW("malloc %d failed\n", length+kChunkHdrLen);
164         goto bail;
165     }
166     set4BE(reply + 0, type);
167     set4BE(reply + 4, length);
168     memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
169 
170     *pReplyBuf = reply;
171     *pReplyLen = length + kChunkHdrLen;
172     result = true;
173 
174     LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
175         (char*) reply, reply, length);
176 
177 bail:
178     dvmReleaseTrackedAlloc((Object*) dataArray, NULL);
179     return result;
180 }
181 
182 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
183 #define CONNECTED       1
184 #define DISCONNECTED    2
185 
186 /*
187  * Broadcast an event to all handlers.
188  */
broadcast(int event)189 static void broadcast(int event)
190 {
191     ClassObject* ddmServerClass;
192     Method* bcast;
193 
194     ddmServerClass =
195         dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
196     if (ddmServerClass == NULL) {
197         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
198         goto bail;
199     }
200     bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V");
201     if (bcast == NULL) {
202         LOGW("Unable to find DdmServer.broadcast\n");
203         goto bail;
204     }
205 
206     Thread* self = dvmThreadSelf();
207 
208     if (self->status != THREAD_RUNNING) {
209         LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status);
210         /* try anyway? */
211     }
212 
213     JValue unused;
214     dvmCallMethod(self, bcast, NULL, &unused, event);
215     if (dvmCheckException(self)) {
216         LOGI("Exception thrown by broadcast(%d)\n", event);
217         dvmLogExceptionStackTrace();
218         dvmClearException(self);
219         goto bail;
220     }
221 
222 bail:
223     ;
224 }
225 
226 /*
227  * First DDM packet has arrived over JDWP.  Notify the press.
228  *
229  * We can do some initialization here too.
230  */
dvmDdmConnected(void)231 void dvmDdmConnected(void)
232 {
233     // TODO: any init
234 
235     LOGV("Broadcasting DDM connect\n");
236     broadcast(CONNECTED);
237 }
238 
239 /*
240  * JDWP connection has dropped.
241  *
242  * Do some cleanup.
243  */
dvmDdmDisconnected(void)244 void dvmDdmDisconnected(void)
245 {
246     LOGV("Broadcasting DDM disconnect\n");
247     broadcast(DISCONNECTED);
248 
249     gDvm.ddmThreadNotification = false;
250 }
251 
252 
253 /*
254  * Turn thread notification on or off.
255  */
dvmDdmSetThreadNotification(bool enable)256 void dvmDdmSetThreadNotification(bool enable)
257 {
258     /*
259      * We lock the thread list to avoid sending duplicate events or missing
260      * a thread change.  We should be okay holding this lock while sending
261      * the messages out.  (We have to hold it while accessing a live thread.)
262      */
263     dvmLockThreadList(NULL);
264     gDvm.ddmThreadNotification = enable;
265 
266     if (enable) {
267         Thread* thread;
268         for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
269             //LOGW("notify %d\n", thread->threadId);
270             dvmDdmSendThreadNotification(thread, true);
271         }
272     }
273 
274     dvmUnlockThreadList();
275 }
276 
277 /*
278  * Send a notification when a thread starts or stops.
279  *
280  * Because we broadcast the full set of threads when the notifications are
281  * first enabled, it's possible for "thread" to be actively executing.
282  */
dvmDdmSendThreadNotification(Thread * thread,bool started)283 void dvmDdmSendThreadNotification(Thread* thread, bool started)
284 {
285     if (!gDvm.ddmThreadNotification)
286         return;
287 
288     StringObject* nameObj = (StringObject*)
289         dvmGetFieldObject(thread->threadObj, gDvm.offJavaLangThread_name);
290 
291     int type, len;
292     u1 buf[256];
293 
294     if (started) {
295         const u2* chars;
296         u2* outChars;
297         size_t stringLen;
298 
299         type = CHUNK_TYPE("THCR");
300 
301         if (nameObj != NULL) {
302             stringLen = dvmStringLen(nameObj);
303             chars = dvmStringChars(nameObj);
304         } else {
305             stringLen = 0;
306             chars = NULL;
307         }
308 
309         /* leave room for the two integer fields */
310         if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
311             stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
312         len = stringLen*2 + sizeof(u4)*2;
313 
314         set4BE(&buf[0x00], thread->threadId);
315         set4BE(&buf[0x04], stringLen);
316 
317         /* copy the UTF-16 string, transforming to big-endian */
318         outChars = (u2*) &buf[0x08];
319         while (stringLen--)
320             set2BE((u1*) (outChars++), *chars++);
321     } else {
322         type = CHUNK_TYPE("THDE");
323 
324         len = 4;
325 
326         set4BE(&buf[0x00], thread->threadId);
327     }
328 
329     dvmDbgDdmSendChunk(type, len, buf);
330 }
331 
332 /*
333  * Send a notification when a thread's name changes.
334  */
dvmDdmSendThreadNameChange(int threadId,StringObject * newName)335 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
336 {
337     if (!gDvm.ddmThreadNotification)
338         return;
339 
340     size_t stringLen = dvmStringLen(newName);
341     const u2* chars = dvmStringChars(newName);
342 
343     /*
344      * Output format:
345      *  (4b) thread ID
346      *  (4b) stringLen
347      *  (xb) string chars
348      */
349     int bufLen = 4 + 4 + (stringLen * 2);
350     u1 buf[bufLen];
351 
352     set4BE(&buf[0x00], threadId);
353     set4BE(&buf[0x04], stringLen);
354     u2* outChars = (u2*) &buf[0x08];
355     while (stringLen--)
356         set2BE((u1*) (outChars++), *chars++);
357 
358     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
359 }
360 
361 /*
362  * Get some per-thread stats.
363  *
364  * This is currently generated by opening the appropriate "stat" file
365  * in /proc and reading the pile of stuff that comes out.
366  */
getThreadStats(pid_t pid,pid_t tid,unsigned long * pUtime,unsigned long * pStime)367 static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
368     unsigned long* pStime)
369 {
370     /*
371     int pid;
372     char comm[128];
373     char state;
374     int ppid, pgrp, session, tty_nr, tpgid;
375     unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
376     long cutime, cstime, priority, nice, zero, itrealvalue;
377     unsigned long starttime, vsize;
378     long rss;
379     unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
380     unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
381     int exit_signal, processor;
382     unsigned long rt_priority, policy;
383 
384     scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
385           "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
386           "%lu %lu %lu %d %d %lu %lu",
387         &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
388         &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
389         &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
390         &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
391         &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
392         &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
393         &rt_priority, &policy);
394     */
395 
396     char nameBuf[64];
397     int i, fd;
398 
399     /*
400      * Open and read the appropriate file.  This is expected to work on
401      * Linux but will fail on other platforms (e.g. Mac sim).
402      */
403     sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
404     fd = open(nameBuf, O_RDONLY);
405     if (fd < 0) {
406         LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
407         return false;
408     }
409 
410     char lineBuf[512];      // > 2x typical
411     int cc;
412     cc = read(fd, lineBuf, sizeof(lineBuf)-1);
413     if (cc <= 0) {
414         LOGI("Unable to read '%s': got %d (errno=%d)\n", nameBuf, cc, errno);
415         close(fd);
416         return false;
417     }
418     lineBuf[cc] = '\0';
419 
420     /*
421      * Skip whitespace-separated tokens.
422      */
423     static const char* kWhitespace = " ";
424     char* cp = lineBuf;
425     for (i = 0; i < 13; i++) {
426         cp += strcspn(cp, kWhitespace);     // skip token
427         cp += strspn(cp, kWhitespace);      // skip whitespace
428     }
429 
430     /*
431      * Grab the values we want.
432      */
433     char* endp;
434     *pUtime = strtoul(cp, &endp, 10);
435     if (endp == cp)
436         LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
437 
438     cp += strcspn(cp, kWhitespace);
439     cp += strspn(cp, kWhitespace);
440 
441     *pStime = strtoul(cp, &endp, 10);
442     if (endp == cp)
443         LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
444 
445     close(fd);
446     return true;
447 }
448 
449 /*
450  * Generate the contents of a THST chunk.  The data encompasses all known
451  * threads.
452  *
453  * Response has:
454  *  (1b) header len
455  *  (1b) bytes per entry
456  *  (2b) thread count
457  * Then, for each thread:
458  *  (4b) threadId
459  *  (1b) thread status
460  *  (4b) tid
461  *  (4b) utime
462  *  (4b) stime
463  *  (1b) is daemon?
464  *
465  * The length fields exist in anticipation of adding additional fields
466  * without wanting to break ddms or bump the full protocol version.  I don't
467  * think it warrants full versioning.  They might be extraneous and could
468  * be removed from a future version.
469  *
470  * Returns a new byte[] with the data inside, or NULL on failure.  The
471  * caller must call dvmReleaseTrackedAlloc() on the array.
472  */
dvmDdmGenerateThreadStats(void)473 ArrayObject* dvmDdmGenerateThreadStats(void)
474 {
475     const int kHeaderLen = 4;
476     const int kBytesPerEntry = 18;
477 
478     dvmLockThreadList(NULL);
479 
480     Thread* thread;
481     int threadCount = 0;
482     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
483         threadCount++;
484 
485     /*
486      * Create a temporary buffer.  We can't perform heap allocation with
487      * the thread list lock held (could cause a GC).  The output is small
488      * enough to sit on the stack.
489      */
490     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
491     u1 tmpBuf[bufLen];
492     u1* buf = tmpBuf;
493 
494     set1(buf+0, kHeaderLen);
495     set1(buf+1, kBytesPerEntry);
496     set2BE(buf+2, (u2) threadCount);
497     buf += kHeaderLen;
498 
499     pid_t pid = getpid();
500     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
501         unsigned long utime, stime;
502         bool isDaemon;
503 
504         if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
505             // failed; drop in empty values
506             utime = stime = 0;
507         }
508 
509         isDaemon = dvmGetFieldBoolean(thread->threadObj,
510                         gDvm.offJavaLangThread_daemon);
511 
512         set4BE(buf+0, thread->threadId);
513         set1(buf+4, thread->status);
514         set4BE(buf+5, thread->systemTid);
515         set4BE(buf+9, utime);
516         set4BE(buf+13, stime);
517         set1(buf+17, isDaemon);
518 
519         buf += kBytesPerEntry;
520     }
521     dvmUnlockThreadList();
522 
523 
524     /*
525      * Create a byte array to hold the data.
526      */
527     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
528     if (arrayObj != NULL)
529         memcpy(arrayObj->contents, tmpBuf, bufLen);
530     return arrayObj;
531 }
532 
533 
534 /*
535  * Find the specified thread and return its stack trace as an array of
536  * StackTraceElement objects.
537  */
dvmDdmGetStackTraceById(u4 threadId)538 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
539 {
540     Thread* self = dvmThreadSelf();
541     Thread* thread;
542     int* traceBuf;
543 
544     dvmLockThreadList(self);
545 
546     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
547         if (thread->threadId == threadId)
548             break;
549     }
550     if (thread == NULL) {
551         LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
552         dvmUnlockThreadList();
553         return NULL;
554     }
555 
556     /*
557      * Suspend the thread, pull out the stack trace, then resume the thread
558      * and release the thread list lock.  If we're being asked to examine
559      * our own stack trace, skip the suspend/resume.
560      */
561     int stackDepth = -1;
562     if (thread != self)
563         dvmSuspendThread(thread);
564     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
565     if (thread != self)
566         dvmResumeThread(thread);
567     dvmUnlockThreadList();
568 
569     /*
570      * Convert the raw buffer into an array of StackTraceElement.
571      */
572     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
573     free(traceBuf);
574     return trace;
575 }
576 
577 /*
578  * Gather up the allocation data and copy it into a byte[].
579  *
580  * Returns NULL on failure with an exception raised.
581  */
dvmDdmGetRecentAllocations(void)582 ArrayObject* dvmDdmGetRecentAllocations(void)
583 {
584     u1* data;
585     size_t len;
586 
587     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
588         /* assume OOM */
589         dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
590         return NULL;
591     }
592 
593     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
594     if (arrayObj != NULL)
595         memcpy(arrayObj->contents, data, len);
596     return arrayObj;
597 }
598 
599