/* * Send a notification when a thread's name changes. */ void dvmDdmSendThreadNameChange(int threadId, StringObject* newName) { if (!gDvm.ddmThreadNotification) return; size_t stringLen = dvmStringLen(newName); const u2* chars = dvmStringChars(newName); /* * 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*) &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(void) { 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; pid_t pid = getpid(); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { unsigned long utime, stime; bool isDaemon; if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) { // failed; drop in empty values utime = stime = 0; } isDaemon = dvmGetFieldBoolean(thread->threadObj, gDvm.offJavaLangThread_daemon); set4BE(buf+0, thread->threadId); set1(buf+4, thread->status); set4BE(buf+5, thread->systemTid); set4BE(buf+9, utime); set4BE(buf+13, 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; }
static void append_chunk(HeapChunkContext *ctx, u1 state, void* ptr, size_t length) { /* Make sure there's enough room left in the buffer. * We need to use two bytes for every fractional 256 * allocation units used by the chunk and 17 bytes for * any header. */ { size_t needed = (((length/ALLOCATION_UNIT_SIZE + 255) / 256) * 2) + 17; size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { flush_hpsg_chunk(ctx); } bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { ALOGW("chunk is too big to transmit (length=%zd, %zd bytes)", length, needed); return; } } if (ctx->needHeader) { /* * Start a new HPSx chunk. */ /* [u4]: heap ID */ set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4; /* [u1]: size of allocation unit, in bytes */ *ctx->p++ = 8; /* [u4]: virtual address of segment start */ set4BE(ctx->p, (uintptr_t)ptr); ctx->p += 4; /* [u4]: offset of this piece (relative to the virtual address) */ set4BE(ctx->p, 0); ctx->p += 4; /* [u4]: length of piece, in allocation units * We won't know this until we're done, so save the offset * and stuff in a dummy value. */ ctx->pieceLenField = ctx->p; set4BE(ctx->p, 0x55555555); ctx->p += 4; ctx->needHeader = false; } /* Write out the chunk description. */ length /= ALLOCATION_UNIT_SIZE; // convert to allocation units ctx->totalAllocationUnits += length; while (length > 256) { *ctx->p++ = state | HPSG_PARTIAL; *ctx->p++ = 255; // length - 1 length -= 256; } *ctx->p++ = state; *ctx->p++ = length - 1; }
/* * 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 = dvmStringLen(nameObj); chars = dvmStringChars(nameObj); } 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*) &buf[0x08]; while (stringLen--) set2BE((u1*) (outChars++), *chars++); } else { type = CHUNK_TYPE("THDE"); len = 4; set4BE(&buf[0x00], thread->threadId); } dvmDbgDdmSendChunk(type, len, buf); }
/* * Write the header into the buffer and send the packet off to the debugger. * * Takes ownership of "pReq" (currently discards it). */ static void eventFinish(JdwpState* state, ExpandBuf* pReq) { u1* buf = expandBufGetBuffer(pReq); set4BE(buf, expandBufGetLength(pReq)); set4BE(buf+4, dvmJdwpNextRequestSerial(state)); set1(buf+8, 0); /* flags */ set1(buf+9, kJdwpEventCommandSet); set1(buf+10, kJdwpCompositeCommand); dvmJdwpSendRequest(state, pReq); expandBufFree(pReq); }
void dvmDdmSendHeapSegments(bool shouldLock, bool native) { u1 heapId[sizeof(u4)]; GcHeap *gcHeap = gDvm.gcHeap; int when, what; bool merge; /* Don't even grab the lock if there's nothing to do when we're called. */ if (!native) { when = gcHeap->ddmHpsgWhen; what = gcHeap->ddmHpsgWhat; if (when == HPSG_WHEN_NEVER) { return; } } else { when = gcHeap->ddmNhsgWhen; what = gcHeap->ddmNhsgWhat; if (when == HPSG_WHEN_NEVER) { return; } } if (shouldLock && !dvmLockHeap()) { LOGW("Can't lock heap for DDM HPSx dump\n"); return; } /* Figure out what kind of chunks we'll be sending. */ if (what == HPSG_WHAT_MERGED_OBJECTS) { merge = true; } else if (what == HPSG_WHAT_DISTINCT_OBJECTS) { merge = false; } else { assert(!"bad HPSG.what value"); return; } /* First, send a heap start chunk. */ set4BE(heapId, DEFAULT_HEAP_ID); dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"), sizeof(u4), heapId); /* Send a series of heap segment chunks. */ walkHeap(merge, native); /* Finally, send a heap end chunk. */ dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"), sizeof(u4), heapId); if (shouldLock) { dvmUnlockHeap(); } }
/* * Send up a chunk of DDM data. * * While this takes the form of a JDWP "event", it doesn't interact with * other debugger traffic, and can't suspend the VM, so we skip all of * the fun event token gymnastics. */ void dvmJdwpDdmSendChunkV(JdwpState* state, int type, const struct iovec* iov, int iovcnt) { u1 header[kJDWPHeaderLen + 8]; size_t dataLen = 0; assert(iov != NULL); assert(iovcnt > 0 && iovcnt < 10); /* * "Wrap" the contents of the iovec with a JDWP/DDMS header. We do * this by creating a new copy of the vector with space for the header. */ struct iovec wrapiov[iovcnt+1]; for (int i = 0; i < iovcnt; i++) { wrapiov[i+1].iov_base = iov[i].iov_base; wrapiov[i+1].iov_len = iov[i].iov_len; dataLen += iov[i].iov_len; } /* form the header (JDWP plus DDMS) */ set4BE(header, sizeof(header) + dataLen); set4BE(header+4, dvmJdwpNextRequestSerial(state)); set1(header+8, 0); /* flags */ set1(header+9, kJDWPDdmCmdSet); set1(header+10, kJDWPDdmCmd); set4BE(header+11, type); set4BE(header+15, dataLen); wrapiov[0].iov_base = header; wrapiov[0].iov_len = sizeof(header); /* * Make sure we're in VMWAIT in case the write blocks. */ int oldStatus = dvmDbgThreadWaiting(); dvmJdwpSendBufferedRequest(state, wrapiov, iovcnt+1); dvmDbgThreadContinuing(oldStatus); }
static void flush_hpsg_chunk(HeapChunkContext *ctx) { /* Patch the "length of piece" field. */ assert(ctx->buf <= ctx->pieceLenField && ctx->pieceLenField <= ctx->p); set4BE(ctx->pieceLenField, ctx->totalAllocationUnits); /* Send the chunk. */ dvmDbgDdmSendChunk(ctx->type, ctx->p - ctx->buf, ctx->buf); /* Reset the context. */ ctx->p = ctx->buf; ctx->totalAllocationUnits = 0; ctx->needHeader = true; ctx->pieceLenField = NULL; }
void dvmDdmSendHeapInfo(int reason, bool shouldLock) { struct timeval now; u8 nowMs; u1 *buf, *b; buf = (u1 *)malloc(HPIF_SIZE(1)); if (buf == NULL) { return; } b = buf; /* If there's a one-shot 'when', reset it. */ if (reason == gDvm.gcHeap->ddmHpifWhen) { if (shouldLock && ! dvmLockHeap()) { ALOGW("%s(): can't lock heap to clear when", __func__); goto skip_when; } if (reason == gDvm.gcHeap->ddmHpifWhen) { if (gDvm.gcHeap->ddmHpifWhen == HPIF_WHEN_NEXT_GC) { gDvm.gcHeap->ddmHpifWhen = HPIF_WHEN_NEVER; } } if (shouldLock) { dvmUnlockHeap(); } } skip_when: /* The current time, in milliseconds since 0:00 GMT, 1/1/70. */ if (gettimeofday(&now, NULL) < 0) { nowMs = 0; } else { nowMs = (u8)now.tv_sec * 1000 + now.tv_usec / 1000; } /* number of heaps */ set4BE(b, 1); b += 4; /* For each heap (of which there is one) */ { /* heap ID */ set4BE(b, DEFAULT_HEAP_ID); b += 4; /* timestamp */ set8BE(b, nowMs); b += 8; /* 'when' value */ *b++ = (u1)reason; /* max allowed heap size in bytes */ set4BE(b, dvmHeapSourceGetMaximumSize()); b += 4; /* current heap size in bytes */ set4BE(b, dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0)); b += 4; /* number of bytes allocated */ set4BE(b, dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0)); b += 4; /* number of objects allocated */ set4BE(b, dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0)); b += 4; } assert((intptr_t)b == (intptr_t)buf + (intptr_t)HPIF_SIZE(1)); dvmDbgDdmSendChunk(CHUNK_TYPE("HPIF"), b - buf, buf); }
static void heap_chunk_callback(const void *chunkptr, size_t chunklen, const void *userptr, size_t userlen, void *arg) { HeapChunkContext *ctx = (HeapChunkContext *)arg; u1 state; UNUSED_PARAMETER(userlen); assert((chunklen & (ALLOCATION_UNIT_SIZE-1)) == 0); /* Make sure there's enough room left in the buffer. * We need to use two bytes for every fractional 256 * allocation units used by the chunk. */ { size_t needed = (((chunklen/ALLOCATION_UNIT_SIZE + 255) / 256) * 2); size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { flush_hpsg_chunk(ctx); } bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { LOGW("chunk is too big to transmit (chunklen=%zd, %zd bytes)\n", chunklen, needed); return; } } //TODO: notice when there's a gap and start a new heap, or at least a new range. if (ctx->needHeader) { /* * Start a new HPSx chunk. */ /* [u4]: heap ID */ set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4; /* [u1]: size of allocation unit, in bytes */ *ctx->p++ = 8; /* [u4]: virtual address of segment start */ set4BE(ctx->p, (uintptr_t)chunkptr); ctx->p += 4; /* [u4]: offset of this piece (relative to the virtual address) */ set4BE(ctx->p, 0); ctx->p += 4; /* [u4]: length of piece, in allocation units * We won't know this until we're done, so save the offset * and stuff in a dummy value. */ ctx->pieceLenField = ctx->p; set4BE(ctx->p, 0x55555555); ctx->p += 4; ctx->needHeader = false; } /* Determine the type of this chunk. */ if (userptr == NULL) { /* It's a free chunk. */ state = HPSG_STATE(SOLIDITY_FREE, 0); } else { const DvmHeapChunk *hc = (const DvmHeapChunk *)userptr; const Object *obj = chunk2ptr(hc); /* If we're looking at the native heap, we'll just return * (SOLIDITY_HARD, KIND_NATIVE) for all allocated chunks */ bool native = ctx->type == CHUNK_TYPE("NHSG"); /* It's an allocated chunk. Figure out what it is. */ //TODO: if ctx.merge, see if this chunk is different from the last chunk. // If it's the same, we should combine them. if (!native && dvmIsValidObject(obj)) { ClassObject *clazz = obj->clazz; if (clazz == NULL) { /* The object was probably just created * but hasn't been initialized yet. */ state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); } else if (clazz == gDvm.unlinkedJavaLangClass || clazz == gDvm.classJavaLangClass) { state = HPSG_STATE(SOLIDITY_HARD, KIND_CLASS_OBJECT); } else if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) { if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) { state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); } else { switch (clazz->elementClass->primitiveType) { case PRIM_BOOLEAN: case PRIM_BYTE: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_1); break; case PRIM_CHAR: case PRIM_SHORT: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_2); break; case PRIM_INT: case PRIM_FLOAT: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); break; case PRIM_DOUBLE: case PRIM_LONG: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_8); break; default: assert(!"Unknown GC heap object type"); state = HPSG_STATE(SOLIDITY_HARD, KIND_UNKNOWN); break; } } } else { state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); } } else { obj = NULL; // it's not actually an object state = HPSG_STATE(SOLIDITY_HARD, KIND_NATIVE); } } /* Write out the chunk description. */ chunklen /= ALLOCATION_UNIT_SIZE; // convert to allocation units ctx->totalAllocationUnits += chunklen; while (chunklen > 256) { *ctx->p++ = state | HPSG_PARTIAL; *ctx->p++ = 255; // length - 1 chunklen -= 256; } *ctx->p++ = state; *ctx->p++ = chunklen - 1; }
/* * Append four big-endian bytes. */ void expandBufAdd4BE(ExpandBuf *pBuf, u4 val) { ensureSpace(pBuf, sizeof(val)); set4BE(pBuf->storage + pBuf->curLen, val); pBuf->curLen += sizeof(val); }
/* * "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; bool result = false; assert(dataLen >= 0); /* * Prep DdmServer. We could throw this in gDvm. */ ClassObject* ddmServerClass; Method* dispatch; ddmServerClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL); if (ddmServerClass == NULL) { LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n"); goto bail; } dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch", "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;"); if (dispatch == NULL) { LOGW("Unable to find DdmServer.dispatch\n"); goto bail; } /* * Prep Chunk. */ int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff; ClassObject* chunkClass; chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL); if (chunkClass == NULL) { LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n"); goto bail; } chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I"); chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B"); chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I"); chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I"); if (chunkTypeOff < 0 || chunkDataOff < 0 || chunkOffsetOff < 0 || chunkLengthOff < 0) { LOGW("Unable to find all chunk fields\n"); 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) { LOGW("array alloc failed (%d)\n", 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) { LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen); goto bail; } /* * Call the handler. */ JValue callRes; dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset, length); if (dvmCheckException(self)) { LOGI("Exception thrown by dispatcher for 0x%08x\n", type); dvmLogExceptionStackTrace(); dvmClearException(self); goto bail; } Object* chunk; ArrayObject* replyData; chunk = (Object*) callRes.l; if (chunk == NULL) goto bail; /* * 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. * (If we do an alloc in here, we need to dvmAddTrackedAlloc 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, chunkTypeOff); replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff); offset = dvmGetFieldInt(chunk, chunkOffsetOff); length = dvmGetFieldInt(chunk, chunkLengthOff); LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n", type, replyData, offset, length); if (length == 0 || replyData == NULL) goto bail; if (offset + length > replyData->length) { LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n", offset, length, replyData->length); goto bail; } u1* reply; reply = (u1*) malloc(length + kChunkHdrLen); if (reply == NULL) { LOGW("malloc %d failed\n", 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; LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n", (char*) reply, reply, length); bail: dvmReleaseTrackedAlloc((Object*) dataArray, NULL); return result; }