OSDN Git Service

Merge "Optimize Class.getMethod() by loading only one method."
[android-x86/dalvik.git] / vm / Ddm.c
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  */
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     /*
51      * Prep DdmServer.  We could throw this in gDvm.
52      */
53     ClassObject* ddmServerClass;
54     Method* dispatch;
55
56     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");
60         goto bail;
61     }
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");
66         goto bail;
67     }
68
69     /*
70      * Prep Chunk.
71      */
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");
77         goto bail;
78     }
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)
85     {
86         LOGW("Unable to find all chunk fields\n");
87         goto bail;
88     }
89
90     /*
91      * The chunk handlers are written in the Java programming language, so
92      * we need to convert the buffer to a byte array.
93      */
94     dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
95     if (dataArray == NULL) {
96         LOGW("array alloc failed (%d)\n", dataLen);
97         dvmClearException(self);
98         goto bail;
99     }
100     memcpy(dataArray->contents, buf, dataLen);
101
102     /*
103      * Run through and find all chunks.  [Currently just find the first.]
104      */
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);
111         goto bail;
112     }
113
114     /*
115      * Call the handler.
116      */
117     JValue callRes;
118     dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset,
119         length);
120     if (dvmCheckException(self)) {
121         LOGI("Exception thrown by dispatcher for 0x%08x\n", type);
122         dvmLogExceptionStackTrace();
123         dvmClearException(self);
124         goto bail;
125     }
126
127     ArrayObject* replyData;
128     chunk = (Object*) callRes.l;
129     if (chunk == NULL)
130         goto bail;
131
132     /* not strictly necessary -- we don't alloc from managed heap here */
133     dvmAddTrackedAlloc(chunk, self);
134
135     /*
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.
139      *
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.
144      *
145      * So we're pretty much stuck with copying data around multiple times.
146      */
147     type = dvmGetFieldInt(chunk, chunkTypeOff);
148     replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff);
149     offset = dvmGetFieldInt(chunk, chunkOffsetOff);
150     length = dvmGetFieldInt(chunk, chunkLengthOff);
151
152     LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
153         type, replyData, offset, length);
154
155     if (length == 0 || replyData == NULL)
156         goto bail;
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);
160         goto bail;
161     }
162
163     u1* reply;
164     reply = (u1*) malloc(length + kChunkHdrLen);
165     if (reply == NULL) {
166         LOGW("malloc %d failed\n", length+kChunkHdrLen);
167         goto bail;
168     }
169     set4BE(reply + 0, type);
170     set4BE(reply + 4, length);
171     memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
172
173     *pReplyBuf = reply;
174     *pReplyLen = length + kChunkHdrLen;
175     result = true;
176
177     LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
178         (char*) reply, reply, length);
179
180 bail:
181     dvmReleaseTrackedAlloc((Object*) dataArray, self);
182     dvmReleaseTrackedAlloc(chunk, self);
183     return result;
184 }
185
186 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
187 #define CONNECTED       1
188 #define DISCONNECTED    2
189
190 /*
191  * Broadcast an event to all handlers.
192  */
193 static void broadcast(int event)
194 {
195     ClassObject* ddmServerClass;
196     Method* bcast;
197
198     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");
202         goto bail;
203     }
204     bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V");
205     if (bcast == NULL) {
206         LOGW("Unable to find DdmServer.broadcast\n");
207         goto bail;
208     }
209
210     Thread* self = dvmThreadSelf();
211
212     if (self->status != THREAD_RUNNING) {
213         LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status);
214         /* try anyway? */
215     }
216
217     JValue unused;
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);
223         goto bail;
224     }
225
226 bail:
227     ;
228 }
229
230 /*
231  * First DDM packet has arrived over JDWP.  Notify the press.
232  *
233  * We can do some initialization here too.
234  */
235 void dvmDdmConnected(void)
236 {
237     // TODO: any init
238
239     LOGV("Broadcasting DDM connect\n");
240     broadcast(CONNECTED);
241 }
242
243 /*
244  * JDWP connection has dropped.
245  *
246  * Do some cleanup.
247  */
248 void dvmDdmDisconnected(void)
249 {
250     LOGV("Broadcasting DDM disconnect\n");
251     broadcast(DISCONNECTED);
252
253     gDvm.ddmThreadNotification = false;
254 }
255
256
257 /*
258  * Turn thread notification on or off.
259  */
260 void dvmDdmSetThreadNotification(bool enable)
261 {
262     /*
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.)
266      */
267     dvmLockThreadList(NULL);
268     gDvm.ddmThreadNotification = enable;
269
270     if (enable) {
271         Thread* thread;
272         for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
273             //LOGW("notify %d\n", thread->threadId);
274             dvmDdmSendThreadNotification(thread, true);
275         }
276     }
277
278     dvmUnlockThreadList();
279 }
280
281 /*
282  * Send a notification when a thread starts or stops.
283  *
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.
286  */
287 void dvmDdmSendThreadNotification(Thread* thread, bool started)
288 {
289     if (!gDvm.ddmThreadNotification)
290         return;
291
292     StringObject* nameObj = NULL;
293     Object* threadObj = thread->threadObj;
294
295     if (threadObj != NULL) {
296         nameObj = (StringObject*)
297             dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
298     }
299
300     int type, len;
301     u1 buf[256];
302
303     if (started) {
304         const u2* chars;
305         u2* outChars;
306         size_t stringLen;
307
308         type = CHUNK_TYPE("THCR");
309
310         if (nameObj != NULL) {
311             stringLen = dvmStringLen(nameObj);
312             chars = dvmStringChars(nameObj);
313         } else {
314             stringLen = 0;
315             chars = NULL;
316         }
317
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;
322
323         set4BE(&buf[0x00], thread->threadId);
324         set4BE(&buf[0x04], stringLen);
325
326         /* copy the UTF-16 string, transforming to big-endian */
327         outChars = (u2*) &buf[0x08];
328         while (stringLen--)
329             set2BE((u1*) (outChars++), *chars++);
330     } else {
331         type = CHUNK_TYPE("THDE");
332
333         len = 4;
334
335         set4BE(&buf[0x00], thread->threadId);
336     }
337
338     dvmDbgDdmSendChunk(type, len, buf);
339 }
340
341 /*
342  * Send a notification when a thread's name changes.
343  */
344 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
345 {
346     if (!gDvm.ddmThreadNotification)
347         return;
348
349     size_t stringLen = dvmStringLen(newName);
350     const u2* chars = dvmStringChars(newName);
351
352     /*
353      * Output format:
354      *  (4b) thread ID
355      *  (4b) stringLen
356      *  (xb) string chars
357      */
358     int bufLen = 4 + 4 + (stringLen * 2);
359     u1 buf[bufLen];
360
361     set4BE(&buf[0x00], threadId);
362     set4BE(&buf[0x04], stringLen);
363     u2* outChars = (u2*) &buf[0x08];
364     while (stringLen--)
365         set2BE((u1*) (outChars++), *chars++);
366
367     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
368 }
369
370 /*
371  * Generate the contents of a THST chunk.  The data encompasses all known
372  * threads.
373  *
374  * Response has:
375  *  (1b) header len
376  *  (1b) bytes per entry
377  *  (2b) thread count
378  * Then, for each thread:
379  *  (4b) threadId
380  *  (1b) thread status
381  *  (4b) tid
382  *  (4b) utime
383  *  (4b) stime
384  *  (1b) is daemon?
385  *
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.
390  *
391  * Returns a new byte[] with the data inside, or NULL on failure.  The
392  * caller must call dvmReleaseTrackedAlloc() on the array.
393  */
394 ArrayObject* dvmDdmGenerateThreadStats(void)
395 {
396     const int kHeaderLen = 4;
397     const int kBytesPerEntry = 18;
398
399     dvmLockThreadList(NULL);
400
401     Thread* thread;
402     int threadCount = 0;
403     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
404         threadCount++;
405
406     /*
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.
410      */
411     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
412     u1 tmpBuf[bufLen];
413     u1* buf = tmpBuf;
414
415     set1(buf+0, kHeaderLen);
416     set1(buf+1, kBytesPerEntry);
417     set2BE(buf+2, (u2) threadCount);
418     buf += kHeaderLen;
419
420     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
421         bool isDaemon = false;
422
423         ProcStatData procStatData;
424         if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
425             /* failed; show zero */
426             memset(&procStatData, 0, sizeof(procStatData));
427         }
428
429         Object* threadObj = thread->threadObj;
430         if (threadObj != NULL) {
431             isDaemon = dvmGetFieldBoolean(threadObj,
432                             gDvm.offJavaLangThread_daemon);
433         }
434
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);
441
442         buf += kBytesPerEntry;
443     }
444     dvmUnlockThreadList();
445
446
447     /*
448      * Create a byte array to hold the data.
449      */
450     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
451     if (arrayObj != NULL)
452         memcpy(arrayObj->contents, tmpBuf, bufLen);
453     return arrayObj;
454 }
455
456
457 /*
458  * Find the specified thread and return its stack trace as an array of
459  * StackTraceElement objects.
460  */
461 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
462 {
463     Thread* self = dvmThreadSelf();
464     Thread* thread;
465     int* traceBuf;
466
467     dvmLockThreadList(self);
468
469     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
470         if (thread->threadId == threadId)
471             break;
472     }
473     if (thread == NULL) {
474         LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
475         dvmUnlockThreadList();
476         return NULL;
477     }
478
479     /*
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.
483      */
484     int stackDepth = -1;
485     if (thread != self)
486         dvmSuspendThread(thread);
487     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
488     if (thread != self)
489         dvmResumeThread(thread);
490     dvmUnlockThreadList();
491
492     /*
493      * Convert the raw buffer into an array of StackTraceElement.
494      */
495     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
496     free(traceBuf);
497     return trace;
498 }
499
500 /*
501  * Gather up the allocation data and copy it into a byte[].
502  *
503  * Returns NULL on failure with an exception raised.
504  */
505 ArrayObject* dvmDdmGetRecentAllocations(void)
506 {
507     u1* data;
508     size_t len;
509
510     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
511         /* assume OOM */
512         dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
513         return NULL;
514     }
515
516     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
517     if (arrayObj != NULL)
518         memcpy(arrayObj->contents, data, len);
519     return arrayObj;
520 }