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  * Handle Dalvik Debug Monitor requests and events.
     18  *
     19  * Remember that all DDM traffic is big-endian since it travels over the
     20  * JDWP connection.
     21  */
     22 #include "Dalvik.h"
     23 
     24 #include <fcntl.h>
     25 #include <errno.h>
     26 
     27 /*
     28  * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
     29  * need to process each, accumulate the replies, and ship the whole thing
     30  * back.
     31  *
     32  * Returns "true" if we have a reply.  The reply buffer is newly allocated,
     33  * and includes the chunk type/length, followed by the data.
     34  *
     35  * TODO: we currently assume that the request and reply include a single
     36  * chunk.  If this becomes inconvenient we will need to adapt.
     37  */
     38 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
     39     int* pReplyLen)
     40 {
     41     Thread* self = dvmThreadSelf();
     42     const int kChunkHdrLen = 8;
     43     ArrayObject* dataArray = NULL;
     44     bool result = false;
     45 
     46     assert(dataLen >= 0);
     47 
     48     /*
     49      * Prep DdmServer.  We could throw this in gDvm.
     50      */
     51     ClassObject* ddmServerClass;
     52     Method* dispatch;
     53 
     54     ddmServerClass =
     55         dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
     56     if (ddmServerClass == NULL) {
     57         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
     58         goto bail;
     59     }
     60     dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch",
     61                     "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
     62     if (dispatch == NULL) {
     63         LOGW("Unable to find DdmServer.dispatch\n");
     64         goto bail;
     65     }
     66 
     67     /*
     68      * Prep Chunk.
     69      */
     70     int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff;
     71     ClassObject* chunkClass;
     72     chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL);
     73     if (chunkClass == NULL) {
     74         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n");
     75         goto bail;
     76     }
     77     chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I");
     78     chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B");
     79     chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I");
     80     chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I");
     81     if (chunkTypeOff < 0 || chunkDataOff < 0 ||
     82         chunkOffsetOff < 0 || chunkLengthOff < 0)
     83     {
     84         LOGW("Unable to find all chunk fields\n");
     85         goto bail;
     86     }
     87 
     88     /*
     89      * The chunk handlers are written in the Java programming language, so
     90      * we need to convert the buffer to a byte array.
     91      */
     92     dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
     93     if (dataArray == NULL) {
     94         LOGW("array alloc failed (%d)\n", dataLen);
     95         dvmClearException(self);
     96         goto bail;
     97     }
     98     memcpy(dataArray->contents, buf, dataLen);
     99 
    100     /*
    101      * Run through and find all chunks.  [Currently just find the first.]
    102      */
    103     unsigned int offset, length, type;
    104     type = get4BE((u1*)dataArray->contents + 0);
    105     length = get4BE((u1*)dataArray->contents + 4);
    106     offset = kChunkHdrLen;
    107     if (offset+length > (unsigned int) dataLen) {
    108         LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen);
    109         goto bail;
    110     }
    111 
    112     /*
    113      * Call the handler.
    114      */
    115     JValue callRes;
    116     dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset,
    117         length);
    118     if (dvmCheckException(self)) {
    119         LOGI("Exception thrown by dispatcher for 0x%08x\n", type);
    120         dvmLogExceptionStackTrace();
    121         dvmClearException(self);
    122         goto bail;
    123     }
    124 
    125     Object* chunk;
    126     ArrayObject* replyData;
    127     chunk = (Object*) callRes.l;
    128     if (chunk == NULL)
    129         goto bail;
    130 
    131     /*
    132      * Pull the pieces out of the chunk.  We copy the results into a
    133      * newly-allocated buffer that the caller can free.  We don't want to
    134      * continue using the Chunk object because nothing has a reference to it.
    135      * (If we do an alloc in here, we need to dvmAddTrackedAlloc it.)
    136      *
    137      * We could avoid this by returning type/data/offset/length and having
    138      * the caller be aware of the object lifetime issues, but that
    139      * integrates the JDWP code more tightly into the VM, and doesn't work
    140      * if we have responses for multiple chunks.
    141      *
    142      * So we're pretty much stuck with copying data around multiple times.
    143      */
    144     type = dvmGetFieldInt(chunk, chunkTypeOff);
    145     replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff);
    146     offset = dvmGetFieldInt(chunk, chunkOffsetOff);
    147     length = dvmGetFieldInt(chunk, chunkLengthOff);
    148 
    149     LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
    150         type, replyData, offset, length);
    151 
    152     if (length == 0 || replyData == NULL)
    153         goto bail;
    154     if (offset + length > replyData->length) {
    155         LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n",
    156             offset, length, replyData->length);
    157         goto bail;
    158     }
    159 
    160     u1* reply;
    161     reply = (u1*) malloc(length + kChunkHdrLen);
    162     if (reply == NULL) {
    163         LOGW("malloc %d failed\n", length+kChunkHdrLen);
    164         goto bail;
    165     }
    166     set4BE(reply + 0, type);
    167     set4BE(reply + 4, length);
    168     memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
    169 
    170     *pReplyBuf = reply;
    171     *pReplyLen = length + kChunkHdrLen;
    172     result = true;
    173 
    174     LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
    175         (char*) reply, reply, length);
    176 
    177 bail:
    178     dvmReleaseTrackedAlloc((Object*) dataArray, NULL);
    179     return result;
    180 }
    181 
    182 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
    183 #define CONNECTED       1
    184 #define DISCONNECTED    2
    185 
    186 /*
    187  * Broadcast an event to all handlers.
    188  */
    189 static void broadcast(int event)
    190 {
    191     ClassObject* ddmServerClass;
    192     Method* bcast;
    193 
    194     ddmServerClass =
    195         dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
    196     if (ddmServerClass == NULL) {
    197         LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
    198         goto bail;
    199     }
    200     bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V");
    201     if (bcast == NULL) {
    202         LOGW("Unable to find DdmServer.broadcast\n");
    203         goto bail;
    204     }
    205 
    206     Thread* self = dvmThreadSelf();
    207 
    208     if (self->status != THREAD_RUNNING) {
    209         LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status);
    210         /* try anyway? */
    211     }
    212 
    213     JValue unused;
    214     dvmCallMethod(self, bcast, NULL, &unused, event);
    215     if (dvmCheckException(self)) {
    216         LOGI("Exception thrown by broadcast(%d)\n", event);
    217         dvmLogExceptionStackTrace();
    218         dvmClearException(self);
    219         goto bail;
    220     }
    221 
    222 bail:
    223     ;
    224 }
    225 
    226 /*
    227  * First DDM packet has arrived over JDWP.  Notify the press.
    228  *
    229  * We can do some initialization here too.
    230  */
    231 void dvmDdmConnected(void)
    232 {
    233     // TODO: any init
    234 
    235     LOGV("Broadcasting DDM connect\n");
    236     broadcast(CONNECTED);
    237 }
    238 
    239 /*
    240  * JDWP connection has dropped.
    241  *
    242  * Do some cleanup.
    243  */
    244 void dvmDdmDisconnected(void)
    245 {
    246     LOGV("Broadcasting DDM disconnect\n");
    247     broadcast(DISCONNECTED);
    248 
    249     gDvm.ddmThreadNotification = false;
    250 }
    251 
    252 
    253 /*
    254  * Turn thread notification on or off.
    255  */
    256 void dvmDdmSetThreadNotification(bool enable)
    257 {
    258     /*
    259      * We lock the thread list to avoid sending duplicate events or missing
    260      * a thread change.  We should be okay holding this lock while sending
    261      * the messages out.  (We have to hold it while accessing a live thread.)
    262      */
    263     dvmLockThreadList(NULL);
    264     gDvm.ddmThreadNotification = enable;
    265 
    266     if (enable) {
    267         Thread* thread;
    268         for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
    269             //LOGW("notify %d\n", thread->threadId);
    270             dvmDdmSendThreadNotification(thread, true);
    271         }
    272     }
    273 
    274     dvmUnlockThreadList();
    275 }
    276 
    277 /*
    278  * Send a notification when a thread starts or stops.
    279  *
    280  * Because we broadcast the full set of threads when the notifications are
    281  * first enabled, it's possible for "thread" to be actively executing.
    282  */
    283 void dvmDdmSendThreadNotification(Thread* thread, bool started)
    284 {
    285     if (!gDvm.ddmThreadNotification)
    286         return;
    287 
    288     StringObject* nameObj = (StringObject*)
    289         dvmGetFieldObject(thread->threadObj, gDvm.offJavaLangThread_name);
    290 
    291     int type, len;
    292     u1 buf[256];
    293 
    294     if (started) {
    295         const u2* chars;
    296         u2* outChars;
    297         size_t stringLen;
    298 
    299         type = CHUNK_TYPE("THCR");
    300 
    301         if (nameObj != NULL) {
    302             stringLen = dvmStringLen(nameObj);
    303             chars = dvmStringChars(nameObj);
    304         } else {
    305             stringLen = 0;
    306             chars = NULL;
    307         }
    308 
    309         /* leave room for the two integer fields */
    310         if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
    311             stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
    312         len = stringLen*2 + sizeof(u4)*2;
    313 
    314         set4BE(&buf[0x00], thread->threadId);
    315         set4BE(&buf[0x04], stringLen);
    316 
    317         /* copy the UTF-16 string, transforming to big-endian */
    318         outChars = (u2*) &buf[0x08];
    319         while (stringLen--)
    320             set2BE((u1*) (outChars++), *chars++);
    321     } else {
    322         type = CHUNK_TYPE("THDE");
    323 
    324         len = 4;
    325 
    326         set4BE(&buf[0x00], thread->threadId);
    327     }
    328 
    329     dvmDbgDdmSendChunk(type, len, buf);
    330 }
    331 
    332 /*
    333  * Send a notification when a thread's name changes.
    334  */
    335 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
    336 {
    337     if (!gDvm.ddmThreadNotification)
    338         return;
    339 
    340     size_t stringLen = dvmStringLen(newName);
    341     const u2* chars = dvmStringChars(newName);
    342 
    343     /*
    344      * Output format:
    345      *  (4b) thread ID
    346      *  (4b) stringLen
    347      *  (xb) string chars
    348      */
    349     int bufLen = 4 + 4 + (stringLen * 2);
    350     u1 buf[bufLen];
    351 
    352     set4BE(&buf[0x00], threadId);
    353     set4BE(&buf[0x04], stringLen);
    354     u2* outChars = (u2*) &buf[0x08];
    355     while (stringLen--)
    356         set2BE((u1*) (outChars++), *chars++);
    357 
    358     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
    359 }
    360 
    361 /*
    362  * Get some per-thread stats.
    363  *
    364  * This is currently generated by opening the appropriate "stat" file
    365  * in /proc and reading the pile of stuff that comes out.
    366  */
    367 static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
    368     unsigned long* pStime)
    369 {
    370     /*
    371     int pid;
    372     char comm[128];
    373     char state;
    374     int ppid, pgrp, session, tty_nr, tpgid;
    375     unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
    376     long cutime, cstime, priority, nice, zero, itrealvalue;
    377     unsigned long starttime, vsize;
    378     long rss;
    379     unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
    380     unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
    381     int exit_signal, processor;
    382     unsigned long rt_priority, policy;
    383 
    384     scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
    385           "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
    386           "%lu %lu %lu %d %d %lu %lu",
    387         &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
    388         &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
    389         &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
    390         &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
    391         &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
    392         &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
    393         &rt_priority, &policy);
    394     */
    395 
    396     char nameBuf[64];
    397     int i, fd;
    398 
    399     /*
    400      * Open and read the appropriate file.  This is expected to work on
    401      * Linux but will fail on other platforms (e.g. Mac sim).
    402      */
    403     sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
    404     fd = open(nameBuf, O_RDONLY);
    405     if (fd < 0) {
    406         LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
    407         return false;
    408     }
    409 
    410     char lineBuf[512];      // > 2x typical
    411     int cc;
    412     cc = read(fd, lineBuf, sizeof(lineBuf)-1);
    413     if (cc <= 0) {
    414         LOGI("Unable to read '%s': got %d (errno=%d)\n", nameBuf, cc, errno);
    415         close(fd);
    416         return false;
    417     }
    418     lineBuf[cc] = '\0';
    419 
    420     /*
    421      * Skip whitespace-separated tokens.
    422      */
    423     static const char* kWhitespace = " ";
    424     char* cp = lineBuf;
    425     for (i = 0; i < 13; i++) {
    426         cp += strcspn(cp, kWhitespace);     // skip token
    427         cp += strspn(cp, kWhitespace);      // skip whitespace
    428     }
    429 
    430     /*
    431      * Grab the values we want.
    432      */
    433     char* endp;
    434     *pUtime = strtoul(cp, &endp, 10);
    435     if (endp == cp)
    436         LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
    437 
    438     cp += strcspn(cp, kWhitespace);
    439     cp += strspn(cp, kWhitespace);
    440 
    441     *pStime = strtoul(cp, &endp, 10);
    442     if (endp == cp)
    443         LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
    444 
    445     close(fd);
    446     return true;
    447 }
    448 
    449 /*
    450  * Generate the contents of a THST chunk.  The data encompasses all known
    451  * threads.
    452  *
    453  * Response has:
    454  *  (1b) header len
    455  *  (1b) bytes per entry
    456  *  (2b) thread count
    457  * Then, for each thread:
    458  *  (4b) threadId
    459  *  (1b) thread status
    460  *  (4b) tid
    461  *  (4b) utime
    462  *  (4b) stime
    463  *  (1b) is daemon?
    464  *
    465  * The length fields exist in anticipation of adding additional fields
    466  * without wanting to break ddms or bump the full protocol version.  I don't
    467  * think it warrants full versioning.  They might be extraneous and could
    468  * be removed from a future version.
    469  *
    470  * Returns a new byte[] with the data inside, or NULL on failure.  The
    471  * caller must call dvmReleaseTrackedAlloc() on the array.
    472  */
    473 ArrayObject* dvmDdmGenerateThreadStats(void)
    474 {
    475     const int kHeaderLen = 4;
    476     const int kBytesPerEntry = 18;
    477 
    478     dvmLockThreadList(NULL);
    479 
    480     Thread* thread;
    481     int threadCount = 0;
    482     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
    483         threadCount++;
    484 
    485     /*
    486      * Create a temporary buffer.  We can't perform heap allocation with
    487      * the thread list lock held (could cause a GC).  The output is small
    488      * enough to sit on the stack.
    489      */
    490     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
    491     u1 tmpBuf[bufLen];
    492     u1* buf = tmpBuf;
    493 
    494     set1(buf+0, kHeaderLen);
    495     set1(buf+1, kBytesPerEntry);
    496     set2BE(buf+2, (u2) threadCount);
    497     buf += kHeaderLen;
    498 
    499     pid_t pid = getpid();
    500     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
    501         unsigned long utime, stime;
    502         bool isDaemon;
    503 
    504         if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
    505             // failed; drop in empty values
    506             utime = stime = 0;
    507         }
    508 
    509         isDaemon = dvmGetFieldBoolean(thread->threadObj,
    510                         gDvm.offJavaLangThread_daemon);
    511 
    512         set4BE(buf+0, thread->threadId);
    513         set1(buf+4, thread->status);
    514         set4BE(buf+5, thread->systemTid);
    515         set4BE(buf+9, utime);
    516         set4BE(buf+13, stime);
    517         set1(buf+17, isDaemon);
    518 
    519         buf += kBytesPerEntry;
    520     }
    521     dvmUnlockThreadList();
    522 
    523 
    524     /*
    525      * Create a byte array to hold the data.
    526      */
    527     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
    528     if (arrayObj != NULL)
    529         memcpy(arrayObj->contents, tmpBuf, bufLen);
    530     return arrayObj;
    531 }
    532 
    533 
    534 /*
    535  * Find the specified thread and return its stack trace as an array of
    536  * StackTraceElement objects.
    537  */
    538 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
    539 {
    540     Thread* self = dvmThreadSelf();
    541     Thread* thread;
    542     int* traceBuf;
    543 
    544     dvmLockThreadList(self);
    545 
    546     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
    547         if (thread->threadId == threadId)
    548             break;
    549     }
    550     if (thread == NULL) {
    551         LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
    552         dvmUnlockThreadList();
    553         return NULL;
    554     }
    555 
    556     /*
    557      * Suspend the thread, pull out the stack trace, then resume the thread
    558      * and release the thread list lock.  If we're being asked to examine
    559      * our own stack trace, skip the suspend/resume.
    560      */
    561     int stackDepth = -1;
    562     if (thread != self)
    563         dvmSuspendThread(thread);
    564     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
    565     if (thread != self)
    566         dvmResumeThread(thread);
    567     dvmUnlockThreadList();
    568 
    569     /*
    570      * Convert the raw buffer into an array of StackTraceElement.
    571      */
    572     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
    573     free(traceBuf);
    574     return trace;
    575 }
    576 
    577 /*
    578  * Gather up the allocation data and copy it into a byte[].
    579  *
    580  * Returns NULL on failure with an exception raised.
    581  */
    582 ArrayObject* dvmDdmGetRecentAllocations(void)
    583 {
    584     u1* data;
    585     size_t len;
    586 
    587     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
    588         /* assume OOM */
    589         dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
    590         return NULL;
    591     }
    592 
    593     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
    594     if (arrayObj != NULL)
    595         memcpy(arrayObj->contents, data, len);
    596     return arrayObj;
    597 }
    598 
    599