text: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  /*
   * Handle Dalvik Debug Monitor requests and events.
   *
   * Remember that all DDM traffic is big-endian since it travels over the
   * JDWP connection.
   */
  #include "Dalvik.h"
  
  #include <fcntl.h>
  #include <errno.h>
  
  /*
   * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
   * need to process each, accumulate the replies, and ship the whole thing
   * back.
   *
   * Returns "true" if we have a reply.  The reply buffer is newly allocated,
   * and includes the chunk type/length, followed by the data.
   *
   * TODO: we currently assume that the request and reply include a single
   * chunk.  If this becomes inconvenient we will need to adapt.
   */
  bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
      int* pReplyLen)
  {
      Thread* self = dvmThreadSelf();
      const int kChunkHdrLen = 8;
      ArrayObject* dataArray = NULL;
      Object* chunk = NULL;
      bool result = false;
  
      assert(dataLen >= 0);
  
      if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) {
          if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) {
              dvmLogExceptionStackTrace();
              dvmClearException(self);
              goto bail;
          }
      }
  
      /*
       * The chunk handlers are written in the Java programming language, so
       * we need to convert the buffer to a byte array.
       */
      dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
      if (dataArray == NULL) {
          ALOGW("array alloc failed (%d)", dataLen);
          dvmClearException(self);
          goto bail;
      }
      memcpy(dataArray->contents, buf, dataLen);
  
      /*
       * Run through and find all chunks.  [Currently just find the first.]
       */
      unsigned int offset, length, type;
      type = get4BE((u1*)dataArray->contents + 0);
      length = get4BE((u1*)dataArray->contents + 4);
      offset = kChunkHdrLen;
      if (offset+length > (unsigned int) dataLen) {
          ALOGW("WARNING: bad chunk found (len=%u pktLen=%d)", length, dataLen);
          goto bail;
      }
  
      /*
       * Call the handler.
       */
      JValue callRes;
      dvmCallMethod(self, gDvm.methDalvikDdmcServer_dispatch, NULL, &callRes,
          type, dataArray, offset, length);
      if (dvmCheckException(self)) {
          ALOGI("Exception thrown by dispatcher for 0x%08x", type);
          dvmLogExceptionStackTrace();
          dvmClearException(self);
          goto bail;
      }
  
      ArrayObject* replyData;
      chunk = (Object*) callRes.l;
      if (chunk == NULL)
          goto bail;
  
      /* not strictly necessary -- we don't alloc from managed heap here */
      dvmAddTrackedAlloc(chunk, self);
  
      /*
       * Pull the pieces out of the chunk.  We copy the results into a
       * newly-allocated buffer that the caller can free.  We don't want to
       * continue using the Chunk object because nothing has a reference to it.
       *
       * We could avoid this by returning type/data/offset/length and having
       * the caller be aware of the object lifetime issues, but that
       * integrates the JDWP code more tightly into the VM, and doesn't work
       * if we have responses for multiple chunks.
       *
       * So we're pretty much stuck with copying data around multiple times.
       */
      type = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_type);
      replyData =
          (ArrayObject*) dvmGetFieldObject(chunk, gDvm.offDalvikDdmcChunk_data);
      offset = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_offset);
      length = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_length);
  
      ALOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d",
          type, replyData, offset, length);
  
      if (length == 0 || replyData == NULL)
          goto bail;
      if (offset + length > replyData->length) {
          ALOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d",
              offset, length, replyData->length);
          goto bail;
      }
  
      u1* reply;
      reply = (u1*) malloc(length + kChunkHdrLen);
      if (reply == NULL) {
          ALOGW("malloc %d failed", length+kChunkHdrLen);
          goto bail;
      }
      set4BE(reply + 0, type);
      set4BE(reply + 4, length);
      memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);
  
      *pReplyBuf = reply;
      *pReplyLen = length + kChunkHdrLen;
      result = true;
  
      ALOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d",
          (char*) reply, reply, length);
  
  bail:
      dvmReleaseTrackedAlloc((Object*) dataArray, self);
      dvmReleaseTrackedAlloc(chunk, self);
      return result;
  }
  
  /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */
  #define CONNECTED       1
  #define DISCONNECTED    2
  
  /*
   * Broadcast an event to all handlers.
   */
  static void broadcast(int event)
  {
      Thread* self = dvmThreadSelf();
  
      if (self->status != THREAD_RUNNING) {
          ALOGE("ERROR: DDM broadcast with thread status=%d", self->status);
          /* try anyway? */
      }
  
      if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) {
          if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) {
              dvmLogExceptionStackTrace();
              dvmClearException(self);
              return;
          }
      }
  
      JValue unused;
      dvmCallMethod(self, gDvm.methDalvikDdmcServer_broadcast, NULL, &unused,
          event);
      if (dvmCheckException(self)) {
          ALOGI("Exception thrown by broadcast(%d)", event);
          dvmLogExceptionStackTrace();
          dvmClearException(self);
          return;
      }
  }
  
  /*
   * First DDM packet has arrived over JDWP.  Notify the press.
   *
   * We can do some initialization here too.
   */
  void dvmDdmConnected()
  {
      // TODO: any init
  
      ALOGV("Broadcasting DDM connect");
      broadcast(CONNECTED);
  }
  
  /*
   * JDWP connection has dropped.
   *
   * Do some cleanup.
   */
  void dvmDdmDisconnected()
  {
      ALOGV("Broadcasting DDM disconnect");
      broadcast(DISCONNECTED);
  
      gDvm.ddmThreadNotification = false;
  }
  
  
  /*
   * Turn thread notification on or off.
   */
  void dvmDdmSetThreadNotification(bool enable)
  {
      /*
       * We lock the thread list to avoid sending duplicate events or missing
       * a thread change.  We should be okay holding this lock while sending
       * the messages out.  (We have to hold it while accessing a live thread.)
       */
      dvmLockThreadList(NULL);
      gDvm.ddmThreadNotification = enable;
  
      if (enable) {
          Thread* thread;
          for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
              //ALOGW("notify %d", thread->threadId);
              dvmDdmSendThreadNotification(thread, true);
          }
      }
  
      dvmUnlockThreadList();
  }
  
  /*
   * Send a notification when a thread starts or stops.
   *
   * Because we broadcast the full set of threads when the notifications are
   * first enabled, it's possible for "thread" to be actively executing.
   */
  void dvmDdmSendThreadNotification(Thread* thread, bool started)
  {
      if (!gDvm.ddmThreadNotification) {
          return;
      }
  
      StringObject* nameObj = NULL;
      Object* threadObj = thread->threadObj;
  
      if (threadObj != NULL) {
          nameObj = (StringObject*)
              dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
      }
  
      int type, len;
      u1 buf[256];
  
      if (started) {
          const u2* chars;
          u2* outChars;
          size_t stringLen;
  
          type = CHUNK_TYPE("THCR");
  
          if (nameObj != NULL) {
              stringLen = nameObj->length();
              chars = nameObj->chars();
          } else {
              stringLen = 0;
              chars = NULL;
          }
  
          /* leave room for the two integer fields */
          if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2) {
              stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
          }
          len = stringLen*2 + sizeof(u4)*2;
  
          set4BE(&buf[0x00], thread->threadId);
          set4BE(&buf[0x04], stringLen);
  
          /* copy the UTF-16 string, transforming to big-endian */
          outChars = (u2*)(void*)&buf[0x08];
          while (stringLen--) {
              set2BE((u1*) (outChars++), *chars++);
          }
      } else {
          type = CHUNK_TYPE("THDE");
  
          len = 4;
  
          set4BE(&buf[0x00], thread->threadId);
      }
  
      dvmDbgDdmSendChunk(type, len, buf);
  }
  
  /*
   * Send a notification when a thread's name changes.
   */
  void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
  {
      if (!gDvm.ddmThreadNotification) {
          return;
      }
  
      size_t stringLen = newName->length();
      const u2* chars = newName->chars();
  
      /*
       * Output format:
       *  (4b) thread ID
       *  (4b) stringLen
       *  (xb) string chars
       */
      int bufLen = 4 + 4 + (stringLen * 2);
      u1 buf[bufLen];
  
      set4BE(&buf[0x00], threadId);
      set4BE(&buf[0x04], stringLen);
      u2* outChars = (u2*)(void*)&buf[0x08];
      while (stringLen--) {
          set2BE((u1*) (outChars++), *chars++);
      }
  
      dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
  }
  
  /*
   * Generate the contents of a THST chunk.  The data encompasses all known
   * threads.
   *
   * Response has:
   *  (1b) header len
   *  (1b) bytes per entry
   *  (2b) thread count
   * Then, for each thread:
   *  (4b) threadId
   *  (1b) thread status
   *  (4b) tid
   *  (4b) utime
   *  (4b) stime
   *  (1b) is daemon?
   *
   * The length fields exist in anticipation of adding additional fields
   * without wanting to break ddms or bump the full protocol version.  I don't
   * think it warrants full versioning.  They might be extraneous and could
   * be removed from a future version.
   *
   * Returns a new byte[] with the data inside, or NULL on failure.  The
   * caller must call dvmReleaseTrackedAlloc() on the array.
   */
  ArrayObject* dvmDdmGenerateThreadStats()
  {
      const int kHeaderLen = 4;
      const int kBytesPerEntry = 18;
  
      dvmLockThreadList(NULL);
  
      Thread* thread;
      int threadCount = 0;
      for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
          threadCount++;
  
      /*
       * Create a temporary buffer.  We can't perform heap allocation with
       * the thread list lock held (could cause a GC).  The output is small
       * enough to sit on the stack.
       */
      int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
      u1 tmpBuf[bufLen];
      u1* buf = tmpBuf;
  
      set1(buf+0, kHeaderLen);
      set1(buf+1, kBytesPerEntry);
      set2BE(buf+2, (u2) threadCount);
      buf += kHeaderLen;
  
      for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
          bool isDaemon = false;
  
          ProcStatData procStatData;
          if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
              /* failed; show zero */
              memset(&procStatData, 0, sizeof(procStatData));
          }
  
          Object* threadObj = thread->threadObj;
          if (threadObj != NULL) {
              isDaemon = dvmGetFieldBoolean(threadObj,
                              gDvm.offJavaLangThread_daemon);
          }
  
          set4BE(buf+0, thread->threadId);
          set1(buf+4, thread->status);
          set4BE(buf+5, thread->systemTid);
          set4BE(buf+9, procStatData.utime);
          set4BE(buf+13, procStatData.stime);
          set1(buf+17, isDaemon);
  
          buf += kBytesPerEntry;
      }
      dvmUnlockThreadList();
  
  
      /*
       * Create a byte array to hold the data.
       */
      ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
      if (arrayObj != NULL)
          memcpy(arrayObj->contents, tmpBuf, bufLen);
      return arrayObj;
  }
  
  
  /*
   * Find the specified thread and return its stack trace as an array of
   * StackTraceElement objects.
   */
  ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
  {
      Thread* self = dvmThreadSelf();
      Thread* thread;
      int* traceBuf;
  
      dvmLockThreadList(self);
  
      for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
          if (thread->threadId == threadId)
              break;
      }
      if (thread == NULL) {
          ALOGI("dvmDdmGetStackTraceById: threadid=%d not found", threadId);
          dvmUnlockThreadList();
          return NULL;
      }
  
      /*
       * Suspend the thread, pull out the stack trace, then resume the thread
       * and release the thread list lock.  If we're being asked to examine
       * our own stack trace, skip the suspend/resume.
       */
      size_t stackDepth;
      if (thread != self)
          dvmSuspendThread(thread);
      traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
      if (thread != self)
          dvmResumeThread(thread);
      dvmUnlockThreadList();
  
      /*
       * Convert the raw buffer into an array of StackTraceElement.
       */
      ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
      free(traceBuf);
      return trace;
  }
  
  /*
   * Gather up the allocation data and copy it into a byte[].
   *
   * Returns NULL on failure with an exception raised.
   */
  ArrayObject* dvmDdmGetRecentAllocations()
  {
      u1* data;
      size_t len;
  
      if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
          /* assume OOM */
          dvmThrowOutOfMemoryError("recent alloc native");
          return NULL;
      }
  
      ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
      if (arrayObj != NULL)
          memcpy(arrayObj->contents, data, len);
      return arrayObj;
  }
  