Home | History | Annotate | Download | only in vm
      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         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 
    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         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  */
    195 void 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  */
    208 void 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  */
    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             //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  */
    247 void 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  */
    307 void 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  */
    359 ArrayObject* 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  */
    426 ArrayObject* 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  */
    470 ArrayObject* 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