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 = NULL;
    289     Object* threadObj = thread->threadObj;
    290 
    291     if (threadObj != NULL) {
    292         nameObj = (StringObject*)
    293             dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
    294     }
    295 
    296     int type, len;
    297     u1 buf[256];
    298 
    299     if (started) {
    300         const u2* chars;
    301         u2* outChars;
    302         size_t stringLen;
    303 
    304         type = CHUNK_TYPE("THCR");
    305 
    306         if (nameObj != NULL) {
    307             stringLen = dvmStringLen(nameObj);
    308             chars = dvmStringChars(nameObj);
    309         } else {
    310             stringLen = 0;
    311             chars = NULL;
    312         }
    313 
    314         /* leave room for the two integer fields */
    315         if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
    316             stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
    317         len = stringLen*2 + sizeof(u4)*2;
    318 
    319         set4BE(&buf[0x00], thread->threadId);
    320         set4BE(&buf[0x04], stringLen);
    321 
    322         /* copy the UTF-16 string, transforming to big-endian */
    323         outChars = (u2*) &buf[0x08];
    324         while (stringLen--)
    325             set2BE((u1*) (outChars++), *chars++);
    326     } else {
    327         type = CHUNK_TYPE("THDE");
    328 
    329         len = 4;
    330 
    331         set4BE(&buf[0x00], thread->threadId);
    332     }
    333 
    334     dvmDbgDdmSendChunk(type, len, buf);
    335 }
    336 
    337 /*
    338  * Send a notification when a thread's name changes.
    339  */
    340 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
    341 {
    342     if (!gDvm.ddmThreadNotification)
    343         return;
    344 
    345     size_t stringLen = dvmStringLen(newName);
    346     const u2* chars = dvmStringChars(newName);
    347 
    348     /*
    349      * Output format:
    350      *  (4b) thread ID
    351      *  (4b) stringLen
    352      *  (xb) string chars
    353      */
    354     int bufLen = 4 + 4 + (stringLen * 2);
    355     u1 buf[bufLen];
    356 
    357     set4BE(&buf[0x00], threadId);
    358     set4BE(&buf[0x04], stringLen);
    359     u2* outChars = (u2*) &buf[0x08];
    360     while (stringLen--)
    361         set2BE((u1*) (outChars++), *chars++);
    362 
    363     dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
    364 }
    365 
    366 /*
    367  * Get some per-thread stats.
    368  *
    369  * This is currently generated by opening the appropriate "stat" file
    370  * in /proc and reading the pile of stuff that comes out.
    371  */
    372 static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
    373     unsigned long* pStime)
    374 {
    375     /*
    376     int pid;
    377     char comm[128];
    378     char state;
    379     int ppid, pgrp, session, tty_nr, tpgid;
    380     unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
    381     long cutime, cstime, priority, nice, zero, itrealvalue;
    382     unsigned long starttime, vsize;
    383     long rss;
    384     unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
    385     unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
    386     int exit_signal, processor;
    387     unsigned long rt_priority, policy;
    388 
    389     scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
    390           "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
    391           "%lu %lu %lu %d %d %lu %lu",
    392         &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
    393         &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
    394         &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
    395         &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
    396         &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
    397         &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
    398         &rt_priority, &policy);
    399     */
    400 
    401     char nameBuf[64];
    402     int i, fd;
    403 
    404     /*
    405      * Open and read the appropriate file.  This is expected to work on
    406      * Linux but will fail on other platforms (e.g. Mac sim).
    407      */
    408     sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
    409     fd = open(nameBuf, O_RDONLY);
    410     if (fd < 0) {
    411         LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
    412         return false;
    413     }
    414 
    415     char lineBuf[512];      // > 2x typical
    416     int cc;
    417     cc = read(fd, lineBuf, sizeof(lineBuf)-1);
    418     if (cc <= 0) {
    419         const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno);
    420         LOGI("Unable to read '%s': %s\n", nameBuf, msg);
    421         close(fd);
    422         return false;
    423     }
    424     lineBuf[cc] = '\0';
    425 
    426     /*
    427      * Skip whitespace-separated tokens.
    428      */
    429     static const char* kWhitespace = " ";
    430     char* cp = lineBuf;
    431     for (i = 0; i < 13; i++) {
    432         cp += strcspn(cp, kWhitespace);     // skip token
    433         cp += strspn(cp, kWhitespace);      // skip whitespace
    434     }
    435 
    436     /*
    437      * Grab the values we want.
    438      */
    439     char* endp;
    440     *pUtime = strtoul(cp, &endp, 10);
    441     if (endp == cp)
    442         LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
    443 
    444     cp += strcspn(cp, kWhitespace);
    445     cp += strspn(cp, kWhitespace);
    446 
    447     *pStime = strtoul(cp, &endp, 10);
    448     if (endp == cp)
    449         LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
    450 
    451     close(fd);
    452     return true;
    453 }
    454 
    455 /*
    456  * Generate the contents of a THST chunk.  The data encompasses all known
    457  * threads.
    458  *
    459  * Response has:
    460  *  (1b) header len
    461  *  (1b) bytes per entry
    462  *  (2b) thread count
    463  * Then, for each thread:
    464  *  (4b) threadId
    465  *  (1b) thread status
    466  *  (4b) tid
    467  *  (4b) utime
    468  *  (4b) stime
    469  *  (1b) is daemon?
    470  *
    471  * The length fields exist in anticipation of adding additional fields
    472  * without wanting to break ddms or bump the full protocol version.  I don't
    473  * think it warrants full versioning.  They might be extraneous and could
    474  * be removed from a future version.
    475  *
    476  * Returns a new byte[] with the data inside, or NULL on failure.  The
    477  * caller must call dvmReleaseTrackedAlloc() on the array.
    478  */
    479 ArrayObject* dvmDdmGenerateThreadStats(void)
    480 {
    481     const int kHeaderLen = 4;
    482     const int kBytesPerEntry = 18;
    483 
    484     dvmLockThreadList(NULL);
    485 
    486     Thread* thread;
    487     int threadCount = 0;
    488     for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
    489         threadCount++;
    490 
    491     /*
    492      * Create a temporary buffer.  We can't perform heap allocation with
    493      * the thread list lock held (could cause a GC).  The output is small
    494      * enough to sit on the stack.
    495      */
    496     int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
    497     u1 tmpBuf[bufLen];
    498     u1* buf = tmpBuf;
    499 
    500     set1(buf+0, kHeaderLen);
    501     set1(buf+1, kBytesPerEntry);
    502     set2BE(buf+2, (u2) threadCount);
    503     buf += kHeaderLen;
    504 
    505     pid_t pid = getpid();
    506     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
    507         unsigned long utime, stime;
    508         bool isDaemon = false;
    509 
    510         if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
    511             // failed; drop in empty values
    512             utime = stime = 0;
    513         }
    514 
    515         Object* threadObj = thread->threadObj;
    516         if (threadObj != NULL) {
    517             isDaemon = dvmGetFieldBoolean(threadObj,
    518                             gDvm.offJavaLangThread_daemon);
    519         }
    520 
    521         set4BE(buf+0, thread->threadId);
    522         set1(buf+4, thread->status);
    523         set4BE(buf+5, thread->systemTid);
    524         set4BE(buf+9, utime);
    525         set4BE(buf+13, stime);
    526         set1(buf+17, isDaemon);
    527 
    528         buf += kBytesPerEntry;
    529     }
    530     dvmUnlockThreadList();
    531 
    532 
    533     /*
    534      * Create a byte array to hold the data.
    535      */
    536     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
    537     if (arrayObj != NULL)
    538         memcpy(arrayObj->contents, tmpBuf, bufLen);
    539     return arrayObj;
    540 }
    541 
    542 
    543 /*
    544  * Find the specified thread and return its stack trace as an array of
    545  * StackTraceElement objects.
    546  */
    547 ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
    548 {
    549     Thread* self = dvmThreadSelf();
    550     Thread* thread;
    551     int* traceBuf;
    552 
    553     dvmLockThreadList(self);
    554 
    555     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
    556         if (thread->threadId == threadId)
    557             break;
    558     }
    559     if (thread == NULL) {
    560         LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
    561         dvmUnlockThreadList();
    562         return NULL;
    563     }
    564 
    565     /*
    566      * Suspend the thread, pull out the stack trace, then resume the thread
    567      * and release the thread list lock.  If we're being asked to examine
    568      * our own stack trace, skip the suspend/resume.
    569      */
    570     int stackDepth = -1;
    571     if (thread != self)
    572         dvmSuspendThread(thread);
    573     traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
    574     if (thread != self)
    575         dvmResumeThread(thread);
    576     dvmUnlockThreadList();
    577 
    578     /*
    579      * Convert the raw buffer into an array of StackTraceElement.
    580      */
    581     ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
    582     free(traceBuf);
    583     return trace;
    584 }
    585 
    586 /*
    587  * Gather up the allocation data and copy it into a byte[].
    588  *
    589  * Returns NULL on failure with an exception raised.
    590  */
    591 ArrayObject* dvmDdmGetRecentAllocations(void)
    592 {
    593     u1* data;
    594     size_t len;
    595 
    596     if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
    597         /* assume OOM */
    598         dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
    599         return NULL;
    600     }
    601 
    602     ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
    603     if (arrayObj != NULL)
    604         memcpy(arrayObj->contents, data, len);
    605     return arrayObj;
    606 }
    607