Ddm.cpp revision 1e1433e78f560a01744e870c19c162ab88df9dc1
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 */
39bool 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
149bail:
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 */
162static 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 */
195void 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 */
208void 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 */
220void 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 */
247void 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 */
304void 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 */
354ArrayObject* 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 */
421ArrayObject* 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 */
465ArrayObject* 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}
481