/*
* 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;
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;
}
/* 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)
{
ClassObject* ddmServerClass;
Method* bcast;
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;
}
bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V");
if (bcast == NULL) {
LOGW("Unable to find DdmServer.broadcast\n");
goto bail;
}
Thread* self = dvmThreadSelf();
if (self->status != THREAD_RUNNING) {
LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status);
/* try anyway? */
}
JValue unused;
dvmCallMethod(self, bcast, NULL, &unused, event);
if (dvmCheckException(self)) {
LOGI("Exception thrown by broadcast(%d)\n", event);
dvmLogExceptionStackTrace();
dvmClearException(self);
goto bail;
}
bail:
;
}
/*
* First DDM packet has arrived over JDWP. Notify the press.
*
* We can do some initialization here too.
*/
void dvmDdmConnected(void)
{
// TODO: any init
LOGV("Broadcasting DDM connect\n");
broadcast(CONNECTED);
}
/*
* JDWP connection has dropped.
*
* Do some cleanup.
*/
void dvmDdmDisconnected(void)
{
LOGV("Broadcasting DDM disconnect\n");
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) {
//LOGW("notify %d\n", 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 = (StringObject*)
dvmGetFieldObject(thread->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);
}
/*
* 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);
}
/*
* Get some per-thread stats.
*
* This is currently generated by opening the appropriate "stat" file
* in /proc and reading the pile of stuff that comes out.
*/
static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
unsigned long* pStime)
{
/*
int pid;
char comm[128];
char state;
int ppid, pgrp, session, tty_nr, tpgid;
unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
long cutime, cstime, priority, nice, zero, itrealvalue;
unsigned long starttime, vsize;
long rss;
unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
int exit_signal, processor;
unsigned long rt_priority, policy;
scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
"%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
"%lu %lu %lu %d %d %lu %lu",
&pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
&flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
&cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
&starttime, &vsize, &rss, &rlim, &startcode, &endcode,
&startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
&sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
&rt_priority, &policy);
*/
char nameBuf[64];
int i, fd;
/*
* Open and read the appropriate file. This is expected to work on
* Linux but will fail on other platforms (e.g. Mac sim).
*/
sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
fd = open(nameBuf, O_RDONLY);
if (fd < 0) {
LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
return false;
}
char lineBuf[512]; // > 2x typical
int cc;
cc = read(fd, lineBuf, sizeof(lineBuf)-1);
if (cc <= 0) {
LOGI("Unable to read '%s': got %d (errno=%d)\n", nameBuf, cc, errno);
close(fd);
return false;
}
lineBuf[cc] = '\0';
/*
* Skip whitespace-separated tokens.
*/
static const char* kWhitespace = " ";
char* cp = lineBuf;
for (i = 0; i < 13; i++) {
cp += strcspn(cp, kWhitespace); // skip token
cp += strspn(cp, kWhitespace); // skip whitespace
}
/*
* Grab the values we want.
*/
char* endp;
*pUtime = strtoul(cp, &endp, 10);
if (endp == cp)
LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
cp += strcspn(cp, kWhitespace);
cp += strspn(cp, kWhitespace);
*pStime = strtoul(cp, &endp, 10);
if (endp == cp)
LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
close(fd);
return true;
}
/*
* 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;
}
/*
* 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) {
LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", 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.
*/
int stackDepth = -1;
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(void)
{
u1* data;
size_t len;
if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
/* assume OOM */
dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
return NULL;
}
ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
if (arrayObj != NULL)
memcpy(arrayObj->contents, data, len);
return arrayObj;
}