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 * Handle Dalvik Debug Monitor requests and events. 18 * 19 * Remember that all DDM traffic is big-endian since it travels over the 20 * JDWP connection. 21 */ 22 #include "Dalvik.h" 23 24 #include <fcntl.h> 25 #include <errno.h> 26 27 /* 28 * "buf" contains a full JDWP packet, possibly with multiple chunks. We 29 * need to process each, accumulate the replies, and ship the whole thing 30 * back. 31 * 32 * Returns "true" if we have a reply. The reply buffer is newly allocated, 33 * and includes the chunk type/length, followed by the data. 34 * 35 * TODO: we currently assume that the request and reply include a single 36 * chunk. If this becomes inconvenient we will need to adapt. 37 */ 38 bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf, 39 int* pReplyLen) 40 { 41 Thread* self = dvmThreadSelf(); 42 const int kChunkHdrLen = 8; 43 ArrayObject* dataArray = NULL; 44 bool result = false; 45 46 assert(dataLen >= 0); 47 48 /* 49 * Prep DdmServer. We could throw this in gDvm. 50 */ 51 ClassObject* ddmServerClass; 52 Method* dispatch; 53 54 ddmServerClass = 55 dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL); 56 if (ddmServerClass == NULL) { 57 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n"); 58 goto bail; 59 } 60 dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch", 61 "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;"); 62 if (dispatch == NULL) { 63 LOGW("Unable to find DdmServer.dispatch\n"); 64 goto bail; 65 } 66 67 /* 68 * Prep Chunk. 69 */ 70 int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff; 71 ClassObject* chunkClass; 72 chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL); 73 if (chunkClass == NULL) { 74 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n"); 75 goto bail; 76 } 77 chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I"); 78 chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B"); 79 chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I"); 80 chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I"); 81 if (chunkTypeOff < 0 || chunkDataOff < 0 || 82 chunkOffsetOff < 0 || chunkLengthOff < 0) 83 { 84 LOGW("Unable to find all chunk fields\n"); 85 goto bail; 86 } 87 88 /* 89 * The chunk handlers are written in the Java programming language, so 90 * we need to convert the buffer to a byte array. 91 */ 92 dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT); 93 if (dataArray == NULL) { 94 LOGW("array alloc failed (%d)\n", dataLen); 95 dvmClearException(self); 96 goto bail; 97 } 98 memcpy(dataArray->contents, buf, dataLen); 99 100 /* 101 * Run through and find all chunks. [Currently just find the first.] 102 */ 103 unsigned int offset, length, type; 104 type = get4BE((u1*)dataArray->contents + 0); 105 length = get4BE((u1*)dataArray->contents + 4); 106 offset = kChunkHdrLen; 107 if (offset+length > (unsigned int) dataLen) { 108 LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen); 109 goto bail; 110 } 111 112 /* 113 * Call the handler. 114 */ 115 JValue callRes; 116 dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset, 117 length); 118 if (dvmCheckException(self)) { 119 LOGI("Exception thrown by dispatcher for 0x%08x\n", type); 120 dvmLogExceptionStackTrace(); 121 dvmClearException(self); 122 goto bail; 123 } 124 125 Object* chunk; 126 ArrayObject* replyData; 127 chunk = (Object*) callRes.l; 128 if (chunk == NULL) 129 goto bail; 130 131 /* 132 * Pull the pieces out of the chunk. We copy the results into a 133 * newly-allocated buffer that the caller can free. We don't want to 134 * continue using the Chunk object because nothing has a reference to it. 135 * (If we do an alloc in here, we need to dvmAddTrackedAlloc it.) 136 * 137 * We could avoid this by returning type/data/offset/length and having 138 * the caller be aware of the object lifetime issues, but that 139 * integrates the JDWP code more tightly into the VM, and doesn't work 140 * if we have responses for multiple chunks. 141 * 142 * So we're pretty much stuck with copying data around multiple times. 143 */ 144 type = dvmGetFieldInt(chunk, chunkTypeOff); 145 replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff); 146 offset = dvmGetFieldInt(chunk, chunkOffsetOff); 147 length = dvmGetFieldInt(chunk, chunkLengthOff); 148 149 LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n", 150 type, replyData, offset, length); 151 152 if (length == 0 || replyData == NULL) 153 goto bail; 154 if (offset + length > replyData->length) { 155 LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n", 156 offset, length, replyData->length); 157 goto bail; 158 } 159 160 u1* reply; 161 reply = (u1*) malloc(length + kChunkHdrLen); 162 if (reply == NULL) { 163 LOGW("malloc %d failed\n", length+kChunkHdrLen); 164 goto bail; 165 } 166 set4BE(reply + 0, type); 167 set4BE(reply + 4, length); 168 memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length); 169 170 *pReplyBuf = reply; 171 *pReplyLen = length + kChunkHdrLen; 172 result = true; 173 174 LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n", 175 (char*) reply, reply, length); 176 177 bail: 178 dvmReleaseTrackedAlloc((Object*) dataArray, NULL); 179 return result; 180 } 181 182 /* defined in org.apache.harmony.dalvik.ddmc.DdmServer */ 183 #define CONNECTED 1 184 #define DISCONNECTED 2 185 186 /* 187 * Broadcast an event to all handlers. 188 */ 189 static void broadcast(int event) 190 { 191 ClassObject* ddmServerClass; 192 Method* bcast; 193 194 ddmServerClass = 195 dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL); 196 if (ddmServerClass == NULL) { 197 LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n"); 198 goto bail; 199 } 200 bcast = dvmFindDirectMethodByDescriptor(ddmServerClass, "broadcast", "(I)V"); 201 if (bcast == NULL) { 202 LOGW("Unable to find DdmServer.broadcast\n"); 203 goto bail; 204 } 205 206 Thread* self = dvmThreadSelf(); 207 208 if (self->status != THREAD_RUNNING) { 209 LOGE("ERROR: DDM broadcast with thread status=%d\n", self->status); 210 /* try anyway? */ 211 } 212 213 JValue unused; 214 dvmCallMethod(self, bcast, NULL, &unused, event); 215 if (dvmCheckException(self)) { 216 LOGI("Exception thrown by broadcast(%d)\n", event); 217 dvmLogExceptionStackTrace(); 218 dvmClearException(self); 219 goto bail; 220 } 221 222 bail: 223 ; 224 } 225 226 /* 227 * First DDM packet has arrived over JDWP. Notify the press. 228 * 229 * We can do some initialization here too. 230 */ 231 void dvmDdmConnected(void) 232 { 233 // TODO: any init 234 235 LOGV("Broadcasting DDM connect\n"); 236 broadcast(CONNECTED); 237 } 238 239 /* 240 * JDWP connection has dropped. 241 * 242 * Do some cleanup. 243 */ 244 void dvmDdmDisconnected(void) 245 { 246 LOGV("Broadcasting DDM disconnect\n"); 247 broadcast(DISCONNECTED); 248 249 gDvm.ddmThreadNotification = false; 250 } 251 252 253 /* 254 * Turn thread notification on or off. 255 */ 256 void dvmDdmSetThreadNotification(bool enable) 257 { 258 /* 259 * We lock the thread list to avoid sending duplicate events or missing 260 * a thread change. We should be okay holding this lock while sending 261 * the messages out. (We have to hold it while accessing a live thread.) 262 */ 263 dvmLockThreadList(NULL); 264 gDvm.ddmThreadNotification = enable; 265 266 if (enable) { 267 Thread* thread; 268 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 269 //LOGW("notify %d\n", thread->threadId); 270 dvmDdmSendThreadNotification(thread, true); 271 } 272 } 273 274 dvmUnlockThreadList(); 275 } 276 277 /* 278 * Send a notification when a thread starts or stops. 279 * 280 * Because we broadcast the full set of threads when the notifications are 281 * first enabled, it's possible for "thread" to be actively executing. 282 */ 283 void dvmDdmSendThreadNotification(Thread* thread, bool started) 284 { 285 if (!gDvm.ddmThreadNotification) 286 return; 287 288 StringObject* nameObj = NULL; 289 Object* threadObj = thread->threadObj; 290 291 if (threadObj != NULL) { 292 nameObj = (StringObject*) 293 dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name); 294 } 295 296 int type, len; 297 u1 buf[256]; 298 299 if (started) { 300 const u2* chars; 301 u2* outChars; 302 size_t stringLen; 303 304 type = CHUNK_TYPE("THCR"); 305 306 if (nameObj != NULL) { 307 stringLen = dvmStringLen(nameObj); 308 chars = dvmStringChars(nameObj); 309 } else { 310 stringLen = 0; 311 chars = NULL; 312 } 313 314 /* leave room for the two integer fields */ 315 if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2) 316 stringLen = (sizeof(buf) - sizeof(u4)*2) / 2; 317 len = stringLen*2 + sizeof(u4)*2; 318 319 set4BE(&buf[0x00], thread->threadId); 320 set4BE(&buf[0x04], stringLen); 321 322 /* copy the UTF-16 string, transforming to big-endian */ 323 outChars = (u2*) &buf[0x08]; 324 while (stringLen--) 325 set2BE((u1*) (outChars++), *chars++); 326 } else { 327 type = CHUNK_TYPE("THDE"); 328 329 len = 4; 330 331 set4BE(&buf[0x00], thread->threadId); 332 } 333 334 dvmDbgDdmSendChunk(type, len, buf); 335 } 336 337 /* 338 * Send a notification when a thread's name changes. 339 */ 340 void dvmDdmSendThreadNameChange(int threadId, StringObject* newName) 341 { 342 if (!gDvm.ddmThreadNotification) 343 return; 344 345 size_t stringLen = dvmStringLen(newName); 346 const u2* chars = dvmStringChars(newName); 347 348 /* 349 * Output format: 350 * (4b) thread ID 351 * (4b) stringLen 352 * (xb) string chars 353 */ 354 int bufLen = 4 + 4 + (stringLen * 2); 355 u1 buf[bufLen]; 356 357 set4BE(&buf[0x00], threadId); 358 set4BE(&buf[0x04], stringLen); 359 u2* outChars = (u2*) &buf[0x08]; 360 while (stringLen--) 361 set2BE((u1*) (outChars++), *chars++); 362 363 dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf); 364 } 365 366 /* 367 * Get some per-thread stats. 368 * 369 * This is currently generated by opening the appropriate "stat" file 370 * in /proc and reading the pile of stuff that comes out. 371 */ 372 static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime, 373 unsigned long* pStime) 374 { 375 /* 376 int pid; 377 char comm[128]; 378 char state; 379 int ppid, pgrp, session, tty_nr, tpgid; 380 unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime; 381 long cutime, cstime, priority, nice, zero, itrealvalue; 382 unsigned long starttime, vsize; 383 long rss; 384 unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip; 385 unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap; 386 int exit_signal, processor; 387 unsigned long rt_priority, policy; 388 389 scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld " 390 "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu " 391 "%lu %lu %lu %d %d %lu %lu", 392 &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, 393 &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime, 394 &cutime, &cstime, &priority, &nice, &zero, &itrealvalue, 395 &starttime, &vsize, &rss, &rlim, &startcode, &endcode, 396 &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore, 397 &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor, 398 &rt_priority, &policy); 399 */ 400 401 char nameBuf[64]; 402 int i, fd; 403 404 /* 405 * Open and read the appropriate file. This is expected to work on 406 * Linux but will fail on other platforms (e.g. Mac sim). 407 */ 408 sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid); 409 fd = open(nameBuf, O_RDONLY); 410 if (fd < 0) { 411 LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno)); 412 return false; 413 } 414 415 char lineBuf[512]; // > 2x typical 416 int cc; 417 cc = read(fd, lineBuf, sizeof(lineBuf)-1); 418 if (cc <= 0) { 419 const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno); 420 LOGI("Unable to read '%s': %s\n", nameBuf, msg); 421 close(fd); 422 return false; 423 } 424 lineBuf[cc] = '\0'; 425 426 /* 427 * Skip whitespace-separated tokens. 428 */ 429 static const char* kWhitespace = " "; 430 char* cp = lineBuf; 431 for (i = 0; i < 13; i++) { 432 cp += strcspn(cp, kWhitespace); // skip token 433 cp += strspn(cp, kWhitespace); // skip whitespace 434 } 435 436 /* 437 * Grab the values we want. 438 */ 439 char* endp; 440 *pUtime = strtoul(cp, &endp, 10); 441 if (endp == cp) 442 LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp); 443 444 cp += strcspn(cp, kWhitespace); 445 cp += strspn(cp, kWhitespace); 446 447 *pStime = strtoul(cp, &endp, 10); 448 if (endp == cp) 449 LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp); 450 451 close(fd); 452 return true; 453 } 454 455 /* 456 * Generate the contents of a THST chunk. The data encompasses all known 457 * threads. 458 * 459 * Response has: 460 * (1b) header len 461 * (1b) bytes per entry 462 * (2b) thread count 463 * Then, for each thread: 464 * (4b) threadId 465 * (1b) thread status 466 * (4b) tid 467 * (4b) utime 468 * (4b) stime 469 * (1b) is daemon? 470 * 471 * The length fields exist in anticipation of adding additional fields 472 * without wanting to break ddms or bump the full protocol version. I don't 473 * think it warrants full versioning. They might be extraneous and could 474 * be removed from a future version. 475 * 476 * Returns a new byte[] with the data inside, or NULL on failure. The 477 * caller must call dvmReleaseTrackedAlloc() on the array. 478 */ 479 ArrayObject* dvmDdmGenerateThreadStats(void) 480 { 481 const int kHeaderLen = 4; 482 const int kBytesPerEntry = 18; 483 484 dvmLockThreadList(NULL); 485 486 Thread* thread; 487 int threadCount = 0; 488 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) 489 threadCount++; 490 491 /* 492 * Create a temporary buffer. We can't perform heap allocation with 493 * the thread list lock held (could cause a GC). The output is small 494 * enough to sit on the stack. 495 */ 496 int bufLen = kHeaderLen + threadCount * kBytesPerEntry; 497 u1 tmpBuf[bufLen]; 498 u1* buf = tmpBuf; 499 500 set1(buf+0, kHeaderLen); 501 set1(buf+1, kBytesPerEntry); 502 set2BE(buf+2, (u2) threadCount); 503 buf += kHeaderLen; 504 505 pid_t pid = getpid(); 506 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 507 unsigned long utime, stime; 508 bool isDaemon = false; 509 510 if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) { 511 // failed; drop in empty values 512 utime = stime = 0; 513 } 514 515 Object* threadObj = thread->threadObj; 516 if (threadObj != NULL) { 517 isDaemon = dvmGetFieldBoolean(threadObj, 518 gDvm.offJavaLangThread_daemon); 519 } 520 521 set4BE(buf+0, thread->threadId); 522 set1(buf+4, thread->status); 523 set4BE(buf+5, thread->systemTid); 524 set4BE(buf+9, utime); 525 set4BE(buf+13, stime); 526 set1(buf+17, isDaemon); 527 528 buf += kBytesPerEntry; 529 } 530 dvmUnlockThreadList(); 531 532 533 /* 534 * Create a byte array to hold the data. 535 */ 536 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT); 537 if (arrayObj != NULL) 538 memcpy(arrayObj->contents, tmpBuf, bufLen); 539 return arrayObj; 540 } 541 542 543 /* 544 * Find the specified thread and return its stack trace as an array of 545 * StackTraceElement objects. 546 */ 547 ArrayObject* dvmDdmGetStackTraceById(u4 threadId) 548 { 549 Thread* self = dvmThreadSelf(); 550 Thread* thread; 551 int* traceBuf; 552 553 dvmLockThreadList(self); 554 555 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 556 if (thread->threadId == threadId) 557 break; 558 } 559 if (thread == NULL) { 560 LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId); 561 dvmUnlockThreadList(); 562 return NULL; 563 } 564 565 /* 566 * Suspend the thread, pull out the stack trace, then resume the thread 567 * and release the thread list lock. If we're being asked to examine 568 * our own stack trace, skip the suspend/resume. 569 */ 570 int stackDepth = -1; 571 if (thread != self) 572 dvmSuspendThread(thread); 573 traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth); 574 if (thread != self) 575 dvmResumeThread(thread); 576 dvmUnlockThreadList(); 577 578 /* 579 * Convert the raw buffer into an array of StackTraceElement. 580 */ 581 ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth); 582 free(traceBuf); 583 return trace; 584 } 585 586 /* 587 * Gather up the allocation data and copy it into a byte[]. 588 * 589 * Returns NULL on failure with an exception raised. 590 */ 591 ArrayObject* dvmDdmGetRecentAllocations(void) 592 { 593 u1* data; 594 size_t len; 595 596 if (!dvmGenerateTrackedAllocationReport(&data, &len)) { 597 /* assume OOM */ 598 dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native"); 599 return NULL; 600 } 601 602 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT); 603 if (arrayObj != NULL) 604 memcpy(arrayObj->contents, data, len); 605 return arrayObj; 606 } 607