OSDN Git Service

am 90232632: Kill stale assert, fix for 4310384
[android-x86/dalvik.git] / vm / Ddm.cpp
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     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         LOGW("array alloc failed (%d)\n", 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         LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", 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         LOGI("Exception thrown by dispatcher for 0x%08x\n", 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     LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
122         type, replyData, offset, length);
123
124     if (length == 0 || replyData == NULL)
125         goto bail;
126     if (offset + length > replyData->length) {
127         LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n",
128             offset, length, replyData->length);
129         goto bail;
130     }
131
132     u1* reply;
133     reply = (u1*) malloc(length + kChunkHdrLen);
134     if (reply == NULL) {
135         LOGW("malloc %d failed\n", 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     LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
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  */
162 static void broadcast(int event)
163 {
164     Thread* self = dvmThreadSelf();
165
166     if (self->status != THREAD_RUNNING) {
167         LOGE("ERROR: DDM broadcast with thread status=%d\n", 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         LOGI("Exception thrown by broadcast(%d)\n", 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  */
195 void dvmDdmConnected()
196 {
197     // TODO: any init
198
199     LOGV("Broadcasting DDM connect\n");
200     broadcast(CONNECTED);
201 }
202
203 /*
204  * JDWP connection has dropped.
205  *
206  * Do some cleanup.
207  */
208 void dvmDdmDisconnected()
209 {
210     LOGV("Broadcasting DDM disconnect\n");
211     broadcast(DISCONNECTED);
212
213     gDvm.ddmThreadNotification = false;
214 }
215
216
217 /*
218  * Turn thread notification on or off.
219  */
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             //LOGW("notify %d\n", 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  */
247 void dvmDdmSendThreadNotification(Thread* thread, bool started)
248 {
249     if (!gDvm.ddmThreadNotification)
250         return;
251
252     StringObject* nameObj = NULL;
253     Object* threadObj = thread->threadObj;
254
255     if (threadObj != NULL) {
256         nameObj = (StringObject*)
257             dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
258     }
259
260     int type, len;
261     u1 buf[256];
262
263     if (started) {
264         const u2* chars;
265         u2* outChars;
266         size_t stringLen;
267
268         type = CHUNK_TYPE("THCR");
269
270         if (nameObj != NULL) {
271             stringLen = dvmStringLen(nameObj);
272             chars = dvmStringChars(nameObj);
273         } else {
274             stringLen = 0;
275             chars = NULL;
276         }
277
278         /* leave room for the two integer fields */
279         if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
280             stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
281         len = stringLen*2 + sizeof(u4)*2;
282
283         set4BE(&buf[0x00], thread->threadId);
284         set4BE(&buf[0x04], stringLen);
285
286         /* copy the UTF-16 string, transforming to big-endian */
287         outChars = (u2*)(void*)&buf[0x08];
288         while (stringLen--)
289             set2BE((u1*) (outChars++), *chars++);
290     } else {
291         type = CHUNK_TYPE("THDE");
292
293         len = 4;
294
295         set4BE(&buf[0x00], thread->threadId);
296     }
297
298     dvmDbgDdmSendChunk(type, len, buf);
299 }
300
301 /*
302  * Send a notification when a thread's name changes.
303  */
304 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
305 {
306     if (!gDvm.ddmThreadNotification)
307         return;
308
309     size_t stringLen = dvmStringLen(newName);
310     const u2* chars = dvmStringChars(newName);
311
312     /*
313      * Output format:
314      *  (4b) thread ID
315      *  (4b) stringLen
316      *  (xb) string chars
317      */
318     int bufLen = 4 + 4 + (stringLen * 2);
319     u1 buf[bufLen];
320
321     set4BE(&buf[0x00], threadId);
322     set4BE(&buf[0x04], stringLen);
323     u2* outChars = (u2*)(void*)&buf[0x08];
324     while (stringLen--)
325         set2BE((u1*) (outChars++), *chars++);
326
327     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
328 }
329
330 /*
331  * Generate the contents of a THST chunk.  The data encompasses all known
332  * threads.
333  *
334  * Response has:
335  *  (1b) header len
336  *  (1b) bytes per entry
337  *  (2b) thread count
338  * Then, for each thread:
339  *  (4b) threadId
340  *  (1b) thread status
341  *  (4b) tid
342  *  (4b) utime
343  *  (4b) stime
344  *  (1b) is daemon?
345  *
346  * The length fields exist in anticipation of adding additional fields
347  * without wanting to break ddms or bump the full protocol version.  I don't
348  * think it warrants full versioning.  They might be extraneous and could
349  * be removed from a future version.
350  *
351  * Returns a new byte[] with the data inside, or NULL on failure.  The
352  * caller must call dvmReleaseTrackedAlloc() on the array.
353  */
354 ArrayObject* dvmDdmGenerateThreadStats()
355 {
356     const int kHeaderLen = 4;
357     const int kBytesPerEntry = 18;
358
359     dvmLockThreadList(NULL);
360
361     Thread* thread;
362     int threadCount = 0;
363     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
364         threadCount++;
365
366     /*
367      * Create a temporary buffer.  We can't perform heap allocation with
368      * the thread list lock held (could cause a GC).  The output is small
369      * enough to sit on the stack.
370      */
371     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
372     u1 tmpBuf[bufLen];
373     u1* buf = tmpBuf;
374
375     set1(buf+0, kHeaderLen);
376     set1(buf+1, kBytesPerEntry);
377     set2BE(buf+2, (u2) threadCount);
378     buf += kHeaderLen;
379
380     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
381         bool isDaemon = false;
382
383         ProcStatData procStatData;
384         if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
385             /* failed; show zero */
386             memset(&procStatData, 0, sizeof(procStatData));
387         }
388
389         Object* threadObj = thread->threadObj;
390         if (threadObj != NULL) {
391             isDaemon = dvmGetFieldBoolean(threadObj,
392                             gDvm.offJavaLangThread_daemon);
393         }
394
395         set4BE(buf+0, thread->threadId);
396         set1(buf+4, thread->status);
397         set4BE(buf+5, thread->systemTid);
398         set4BE(buf+9, procStatData.utime);
399         set4BE(buf+13, procStatData.stime);
400         set1(buf+17, isDaemon);
401
402         buf += kBytesPerEntry;
403     }
404     dvmUnlockThreadList();
405
406
407     /*
408      * Create a byte array to hold the data.
409      */
410     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
411     if (arrayObj != NULL)
412         memcpy(arrayObj->contents, tmpBuf, bufLen);
413     return arrayObj;
414 }
415
416
417 /*
418  * Find the specified thread and return its stack trace as an array of
419  * StackTraceElement objects.
420  */
421 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
422 {
423     Thread* self = dvmThreadSelf();
424     Thread* thread;
425     int* traceBuf;
426
427     dvmLockThreadList(self);
428
429     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
430         if (thread->threadId == threadId)
431             break;
432     }
433     if (thread == NULL) {
434         LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
435         dvmUnlockThreadList();
436         return NULL;
437     }
438
439     /*
440      * Suspend the thread, pull out the stack trace, then resume the thread
441      * and release the thread list lock.  If we're being asked to examine
442      * our own stack trace, skip the suspend/resume.
443      */
444     size_t stackDepth;
445     if (thread != self)
446         dvmSuspendThread(thread);
447     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
448     if (thread != self)
449         dvmResumeThread(thread);
450     dvmUnlockThreadList();
451
452     /*
453      * Convert the raw buffer into an array of StackTraceElement.
454      */
455     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
456     free(traceBuf);
457     return trace;
458 }
459
460 /*
461  * Gather up the allocation data and copy it into a byte[].
462  *
463  * Returns NULL on failure with an exception raised.
464  */
465 ArrayObject* dvmDdmGetRecentAllocations()
466 {
467     u1* data;
468     size_t len;
469
470     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
471         /* assume OOM */
472         dvmThrowOutOfMemoryError("recent alloc native");
473         return NULL;
474     }
475
476     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
477     if (arrayObj != NULL)
478         memcpy(arrayObj->contents, data, len);
479     return arrayObj;
480 }