/*
* 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;
}