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 /* 18 * Handle Dalvik Debug Monitor requests and events. 19 * 20 * Remember that all DDM traffic is big-endian since it travels over the 21 * JDWP connection. 22 */ 23 #include "Dalvik.h" 24 25 #include <fcntl.h> 26 #include <errno.h> 27 28 /* 29 * "buf" contains a full JDWP packet, possibly with multiple chunks. We 30 * need to process each, accumulate the replies, and ship the whole thing 31 * back. 32 * 33 * Returns "true" if we have a reply. The reply buffer is newly allocated, 34 * and includes the chunk type/length, followed by the data. 35 * 36 * TODO: we currently assume that the request and reply include a single 37 * chunk. If this becomes inconvenient we will need to adapt. 38 */ 39 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf, 40 int* pReplyLen) 41 { 42 Thread* self = dvmThreadSelf(); 43 const int kChunkHdrLen = 8; 44 ArrayObject* dataArray = NULL; 45 Object* chunk = NULL; 46 bool result = false; 47 48 assert(dataLen >= 0); 49 50 if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) { 51 if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcChunk)) { 52 dvmLogExceptionStackTrace(); 53 dvmClearException(self); 54 goto bail; 55 } 56 } 57 58 /* 59 * The chunk handlers are written in the Java programming language, so 60 * we need to convert the buffer to a byte array. 61 */ 62 dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT); 63 if (dataArray == NULL) { 64 ALOGW("array alloc failed (%d)", dataLen); 65 dvmClearException(self); 66 goto bail; 67 } 68 memcpy(dataArray->contents, buf, dataLen); 69 70 /* 71 * Run through and find all chunks. [Currently just find the first.] 72 */ 73 unsigned int offset, length, type; 74 type = get4BE((u1*)dataArray->contents + 0); 75 length = get4BE((u1*)dataArray->contents + 4); 76 offset = kChunkHdrLen; 77 if (offset+length > (unsigned int) dataLen) { 78 ALOGW("WARNING: bad chunk found (len=%u pktLen=%d)", length, dataLen); 79 goto bail; 80 } 81 82 /* 83 * Call the handler. 84 */ 85 JValue callRes; 86 dvmCallMethod(self, gDvm.methDalvikDdmcServer_dispatch, NULL, &callRes, 87 type, dataArray, offset, length); 88 if (dvmCheckException(self)) { 89 ALOGI("Exception thrown by dispatcher for 0x%08x", type); 90 dvmLogExceptionStackTrace(); 91 dvmClearException(self); 92 goto bail; 93 } 94 95 ArrayObject* replyData; 96 chunk = (Object*) callRes.l; 97 if (chunk == NULL) 98 goto bail; 99 100 /* not strictly necessary -- we don't alloc from managed heap here */ 101 dvmAddTrackedAlloc(chunk, self); 102 103 /* 104 * Pull the pieces out of the chunk. We copy the results into a 105 * newly-allocated buffer that the caller can free. We don't want to 106 * continue using the Chunk object because nothing has a reference to it. 107 * 108 * We could avoid this by returning type/data/offset/length and having 109 * the caller be aware of the object lifetime issues, but that 110 * integrates the JDWP code more tightly into the VM, and doesn't work 111 * if we have responses for multiple chunks. 112 * 113 * So we're pretty much stuck with copying data around multiple times. 114 */ 115 type = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_type); 116 replyData = 117 (ArrayObject*) dvmGetFieldObject(chunk, gDvm.offDalvikDdmcChunk_data); 118 offset = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_offset); 119 length = dvmGetFieldInt(chunk, gDvm.offDalvikDdmcChunk_length); 120 121 ALOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d", 122 type, replyData, offset, length); 123 124 if (length == 0 || replyData == NULL) 125 goto bail; 126 if (offset + length > replyData->length) { 127 ALOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d", 128 offset, length, replyData->length); 129 goto bail; 130 } 131 132 u1* reply; 133 reply = (u1*) malloc(length + kChunkHdrLen); 134 if (reply == NULL) { 135 ALOGW("malloc %d failed", length+kChunkHdrLen); 136 goto bail; 137 } 138 set4BE(reply + 0, type); 139 set4BE(reply + 4, length); 140 memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length); 141 142 *pReplyBuf = reply; 143 *pReplyLen = length + kChunkHdrLen; 144 result = true; 145 146 ALOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d", 147 (char*) reply, reply, length); 148 149 bail: 150 dvmReleaseTrackedAlloc((Object*) dataArray, self); 151 dvmReleaseTrackedAlloc(chunk, self); 152 return result; 153 } 154 155 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */ 156 #define CONNECTED 1 157 #define DISCONNECTED 2 158 159 /* 160 * Broadcast an event to all handlers. 161 */ 162 static void broadcast(int event) 163 { 164 Thread* self = dvmThreadSelf(); 165 166 if (self->status != THREAD_RUNNING) { 167 ALOGE("ERROR: DDM broadcast with thread status=%d", self->status); 168 /* try anyway? */ 169 } 170 171 if (!dvmIsClassInitialized(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) { 172 if (!dvmInitClass(gDvm.classOrgApacheHarmonyDalvikDdmcDdmServer)) { 173 dvmLogExceptionStackTrace(); 174 dvmClearException(self); 175 return; 176 } 177 } 178 179 JValue unused; 180 dvmCallMethod(self, gDvm.methDalvikDdmcServer_broadcast, NULL, &unused, 181 event); 182 if (dvmCheckException(self)) { 183 ALOGI("Exception thrown by broadcast(%d)", event); 184 dvmLogExceptionStackTrace(); 185 dvmClearException(self); 186 return; 187 } 188 } 189 190 /* 191 * First DDM packet has arrived over JDWP. Notify the press. 192 * 193 * We can do some initialization here too. 194 */ 195 void dvmDdmConnected() 196 { 197 // TODO: any init 198 199 ALOGV("Broadcasting DDM connect"); 200 broadcast(CONNECTED); 201 } 202 203 /* 204 * JDWP connection has dropped. 205 * 206 * Do some cleanup. 207 */ 208 void dvmDdmDisconnected() 209 { 210 ALOGV("Broadcasting DDM disconnect"); 211 broadcast(DISCONNECTED); 212 213 gDvm.ddmThreadNotification = false; 214 } 215 216 217 /* 218 * Turn thread notification on or off. 219 */ 220 void dvmDdmSetThreadNotification(bool enable) 221 { 222 /* 223 * We lock the thread list to avoid sending duplicate events or missing 224 * a thread change. We should be okay holding this lock while sending 225 * the messages out. (We have to hold it while accessing a live thread.) 226 */ 227 dvmLockThreadList(NULL); 228 gDvm.ddmThreadNotification = enable; 229 230 if (enable) { 231 Thread* thread; 232 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 233 //ALOGW("notify %d", thread->threadId); 234 dvmDdmSendThreadNotification(thread, true); 235 } 236 } 237 238 dvmUnlockThreadList(); 239 } 240 241 /* 242 * Send a notification when a thread starts or stops. 243 * 244 * Because we broadcast the full set of threads when the notifications are 245 * first enabled, it's possible for "thread" to be actively executing. 246 */ 247 void dvmDdmSendThreadNotification(Thread* thread, bool started) 248 { 249 if (!gDvm.ddmThreadNotification) { 250 return; 251 } 252 253 StringObject* nameObj = NULL; 254 Object* threadObj = thread->threadObj; 255 256 if (threadObj != NULL) { 257 nameObj = (StringObject*) 258 dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name); 259 } 260 261 int type, len; 262 u1 buf[256]; 263 264 if (started) { 265 const u2* chars; 266 u2* outChars; 267 size_t stringLen; 268 269 type = CHUNK_TYPE("THCR"); 270 271 if (nameObj != NULL) { 272 stringLen = nameObj->length(); 273 chars = nameObj->chars(); 274 } else { 275 stringLen = 0; 276 chars = NULL; 277 } 278 279 /* leave room for the two integer fields */ 280 if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2) { 281 stringLen = (sizeof(buf) - sizeof(u4)*2) / 2; 282 } 283 len = stringLen*2 + sizeof(u4)*2; 284 285 set4BE(&buf[0x00], thread->threadId); 286 set4BE(&buf[0x04], stringLen); 287 288 /* copy the UTF-16 string, transforming to big-endian */ 289 outChars = (u2*)(void*)&buf[0x08]; 290 while (stringLen--) { 291 set2BE((u1*) (outChars++), *chars++); 292 } 293 } else { 294 type = CHUNK_TYPE("THDE"); 295 296 len = 4; 297 298 set4BE(&buf[0x00], thread->threadId); 299 } 300 301 dvmDbgDdmSendChunk(type, len, buf); 302 } 303 304 /* 305 * Send a notification when a thread's name changes. 306 */ 307 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName) 308 { 309 if (!gDvm.ddmThreadNotification) { 310 return; 311 } 312 313 size_t stringLen = newName->length(); 314 const u2* chars = newName->chars(); 315 316 /* 317 * Output format: 318 * (4b) thread ID 319 * (4b) stringLen 320 * (xb) string chars 321 */ 322 int bufLen = 4 + 4 + (stringLen * 2); 323 u1 buf[bufLen]; 324 325 set4BE(&buf[0x00], threadId); 326 set4BE(&buf[0x04], stringLen); 327 u2* outChars = (u2*)(void*)&buf[0x08]; 328 while (stringLen--) { 329 set2BE((u1*) (outChars++), *chars++); 330 } 331 332 dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf); 333 } 334 335 /* 336 * Generate the contents of a THST chunk. The data encompasses all known 337 * threads. 338 * 339 * Response has: 340 * (1b) header len 341 * (1b) bytes per entry 342 * (2b) thread count 343 * Then, for each thread: 344 * (4b) threadId 345 * (1b) thread status 346 * (4b) tid 347 * (4b) utime 348 * (4b) stime 349 * (1b) is daemon? 350 * 351 * The length fields exist in anticipation of adding additional fields 352 * without wanting to break ddms or bump the full protocol version. I don't 353 * think it warrants full versioning. They might be extraneous and could 354 * be removed from a future version. 355 * 356 * Returns a new byte[] with the data inside, or NULL on failure. The 357 * caller must call dvmReleaseTrackedAlloc() on the array. 358 */ 359 ArrayObject* dvmDdmGenerateThreadStats() 360 { 361 const int kHeaderLen = 4; 362 const int kBytesPerEntry = 18; 363 364 dvmLockThreadList(NULL); 365 366 Thread* thread; 367 int threadCount = 0; 368 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) 369 threadCount++; 370 371 /* 372 * Create a temporary buffer. We can't perform heap allocation with 373 * the thread list lock held (could cause a GC). The output is small 374 * enough to sit on the stack. 375 */ 376 int bufLen = kHeaderLen + threadCount * kBytesPerEntry; 377 u1 tmpBuf[bufLen]; 378 u1* buf = tmpBuf; 379 380 set1(buf+0, kHeaderLen); 381 set1(buf+1, kBytesPerEntry); 382 set2BE(buf+2, (u2) threadCount); 383 buf += kHeaderLen; 384 385 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 386 bool isDaemon = false; 387 388 ProcStatData procStatData; 389 if (!dvmGetThreadStats(&procStatData, thread->systemTid)) { 390 /* failed; show zero */ 391 memset(&procStatData, 0, sizeof(procStatData)); 392 } 393 394 Object* threadObj = thread->threadObj; 395 if (threadObj != NULL) { 396 isDaemon = dvmGetFieldBoolean(threadObj, 397 gDvm.offJavaLangThread_daemon); 398 } 399 400 set4BE(buf+0, thread->threadId); 401 set1(buf+4, thread->status); 402 set4BE(buf+5, thread->systemTid); 403 set4BE(buf+9, procStatData.utime); 404 set4BE(buf+13, procStatData.stime); 405 set1(buf+17, isDaemon); 406 407 buf += kBytesPerEntry; 408 } 409 dvmUnlockThreadList(); 410 411 412 /* 413 * Create a byte array to hold the data. 414 */ 415 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT); 416 if (arrayObj != NULL) 417 memcpy(arrayObj->contents, tmpBuf, bufLen); 418 return arrayObj; 419 } 420 421 422 /* 423 * Find the specified thread and return its stack trace as an array of 424 * StackTraceElement objects. 425 */ 426 ArrayObject* dvmDdmGetStackTraceById(u4 threadId) 427 { 428 Thread* self = dvmThreadSelf(); 429 Thread* thread; 430 int* traceBuf; 431 432 dvmLockThreadList(self); 433 434 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 435 if (thread->threadId == threadId) 436 break; 437 } 438 if (thread == NULL) { 439 ALOGI("dvmDdmGetStackTraceById: threadid=%d not found", threadId); 440 dvmUnlockThreadList(); 441 return NULL; 442 } 443 444 /* 445 * Suspend the thread, pull out the stack trace, then resume the thread 446 * and release the thread list lock. If we're being asked to examine 447 * our own stack trace, skip the suspend/resume. 448 */ 449 size_t stackDepth; 450 if (thread != self) 451 dvmSuspendThread(thread); 452 traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth); 453 if (thread != self) 454 dvmResumeThread(thread); 455 dvmUnlockThreadList(); 456 457 /* 458 * Convert the raw buffer into an array of StackTraceElement. 459 */ 460 ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth); 461 free(traceBuf); 462 return trace; 463 } 464 465 /* 466 * Gather up the allocation data and copy it into a byte[]. 467 * 468 * Returns NULL on failure with an exception raised. 469 */ 470 ArrayObject* dvmDdmGetRecentAllocations() 471 { 472 u1* data; 473 size_t len; 474 475 if (!dvmGenerateTrackedAllocationReport(&data, &len)) { 476 /* assume OOM */ 477 dvmThrowOutOfMemoryError("recent alloc native"); 478 return NULL; 479 } 480 481 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT); 482 if (arrayObj != NULL) 483 memcpy(arrayObj->contents, data, len); 484 return arrayObj; 485 } 486