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        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
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        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 */
195void 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 */
208void 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 */
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            //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 */
247void 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 */
307void 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 */
359ArrayObject* 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 */
426ArrayObject* 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 */
470ArrayObject* 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