blob: 76cbe82f23cee0de99bbdd571a97cf3f1c5385ce [file] [log] [blame]
The Android Open Source Project2ad60cf2008-10-21 07:00:00 -07001/*
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 */
38bool 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
177bail:
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 */
189static 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
222bail:
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 */
231void 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 */
244void 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 */
256void 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 */
283void dvmDdmSendThreadNotification(Thread* thread, bool started)
284{
285 if (!gDvm.ddmThreadNotification)
286 return;
287
288 StringObject* nameObj = (StringObject*)
289 dvmGetFieldObject(thread->threadObj, gDvm.offJavaLangThread_name);
290
291 int type, len;
292 u1 buf[256];
293
294 if (started) {
295 const u2* chars;
296 u2* outChars;
297 size_t stringLen;
298
299 type = CHUNK_TYPE("THCR");
300
301 if (nameObj != NULL) {
302 stringLen = dvmStringLen(nameObj);
303 chars = dvmStringChars(nameObj);
304 } else {
305 stringLen = 0;
306 chars = NULL;
307 }
308
309 /* leave room for the two integer fields */
310 if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
311 stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
312 len = stringLen*2 + sizeof(u4)*2;
313
314 set4BE(&buf[0x00], thread->threadId);
315 set4BE(&buf[0x04], stringLen);
316
317 /* copy the UTF-16 string, transforming to big-endian */
318 outChars = (u2*) &buf[0x08];
319 while (stringLen--)
320 set2BE((u1*) (outChars++), *chars++);
321 } else {
322 type = CHUNK_TYPE("THDE");
323
324 len = 4;
325
326 set4BE(&buf[0x00], thread->threadId);
327 }
328
329 dvmDbgDdmSendChunk(type, len, buf);
330}
331
332/*
333 * Send a notification when a thread's name changes.
334 */
335void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
336{
337 if (!gDvm.ddmThreadNotification)
338 return;
339
340 size_t stringLen = dvmStringLen(newName);
341 const u2* chars = dvmStringChars(newName);
342
343 /*
344 * Output format:
345 * (4b) thread ID
346 * (4b) stringLen
347 * (xb) string chars
348 */
349 int bufLen = 4 + 4 + (stringLen * 2);
350 u1 buf[bufLen];
351
352 set4BE(&buf[0x00], threadId);
353 set4BE(&buf[0x04], stringLen);
354 u2* outChars = (u2*) &buf[0x08];
355 while (stringLen--)
356 set2BE((u1*) (outChars++), *chars++);
357
358 dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
359}
360
361/*
362 * Get some per-thread stats.
363 *
364 * This is currently generated by opening the appropriate "stat" file
365 * in /proc and reading the pile of stuff that comes out.
366 */
367static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
368 unsigned long* pStime)
369{
370 /*
371 int pid;
372 char comm[128];
373 char state;
374 int ppid, pgrp, session, tty_nr, tpgid;
375 unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
376 long cutime, cstime, priority, nice, zero, itrealvalue;
377 unsigned long starttime, vsize;
378 long rss;
379 unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
380 unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
381 int exit_signal, processor;
382 unsigned long rt_priority, policy;
383
384 scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
385 "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
386 "%lu %lu %lu %d %d %lu %lu",
387 &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
388 &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
389 &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
390 &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
391 &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
392 &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
393 &rt_priority, &policy);
394 */
395
396 char nameBuf[64];
397 int i, fd;
398
399 /*
400 * Open and read the appropriate file. This is expected to work on
401 * Linux but will fail on other platforms (e.g. Mac sim).
402 */
403 sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
404 fd = open(nameBuf, O_RDONLY);
405 if (fd < 0) {
406 LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
407 return false;
408 }
409
410 char lineBuf[512]; // > 2x typical
411 int cc;
412 cc = read(fd, lineBuf, sizeof(lineBuf)-1);
413 if (cc <= 0) {
414 LOGI("Unable to read '%s': got %d (errno=%d)\n", nameBuf, cc, errno);
415 close(fd);
416 return false;
417 }
418 lineBuf[cc] = '\0';
419
420 /*
421 * Skip whitespace-separated tokens.
422 */
423 static const char* kWhitespace = " ";
424 char* cp = lineBuf;
425 for (i = 0; i < 13; i++) {
426 cp += strcspn(cp, kWhitespace); // skip token
427 cp += strspn(cp, kWhitespace); // skip whitespace
428 }
429
430 /*
431 * Grab the values we want.
432 */
433 char* endp;
434 *pUtime = strtoul(cp, &endp, 10);
435 if (endp == cp)
436 LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
437
438 cp += strcspn(cp, kWhitespace);
439 cp += strspn(cp, kWhitespace);
440
441 *pStime = strtoul(cp, &endp, 10);
442 if (endp == cp)
443 LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
444
445 close(fd);
446 return true;
447}
448
449/*
450 * Generate the contents of a THST chunk. The data encompasses all known
451 * threads.
452 *
453 * Response has:
454 * (1b) header len
455 * (1b) bytes per entry
456 * (2b) thread count
457 * Then, for each thread:
458 * (4b) threadId
459 * (1b) thread status
460 * (4b) tid
461 * (4b) utime
462 * (4b) stime
463 * (1b) is daemon?
464 *
465 * The length fields exist in anticipation of adding additional fields
466 * without wanting to break ddms or bump the full protocol version. I don't
467 * think it warrants full versioning. They might be extraneous and could
468 * be removed from a future version.
469 *
470 * Returns a new byte[] with the data inside, or NULL on failure. The
471 * caller must call dvmReleaseTrackedAlloc() on the array.
472 */
473ArrayObject* dvmDdmGenerateThreadStats(void)
474{
475 const int kHeaderLen = 4;
476 const int kBytesPerEntry = 18;
477
478 dvmLockThreadList(NULL);
479
480 Thread* thread;
481 int threadCount = 0;
482 for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
483 threadCount++;
484
485 /*
486 * Create a temporary buffer. We can't perform heap allocation with
487 * the thread list lock held (could cause a GC). The output is small
488 * enough to sit on the stack.
489 */
490 int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
491 u1 tmpBuf[bufLen];
492 u1* buf = tmpBuf;
493
494 set1(buf+0, kHeaderLen);
495 set1(buf+1, kBytesPerEntry);
496 set2BE(buf+2, (u2) threadCount);
497 buf += kHeaderLen;
498
499 pid_t pid = getpid();
500 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
501 unsigned long utime, stime;
502 bool isDaemon;
503
504 if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
505 // failed; drop in empty values
506 utime = stime = 0;
507 }
508
509 isDaemon = dvmGetFieldBoolean(thread->threadObj,
510 gDvm.offJavaLangThread_daemon);
511
512 set4BE(buf+0, thread->threadId);
513 set1(buf+4, thread->status);
514 set4BE(buf+5, thread->systemTid);
515 set4BE(buf+9, utime);
516 set4BE(buf+13, stime);
517 set1(buf+17, isDaemon);
518
519 buf += kBytesPerEntry;
520 }
521 dvmUnlockThreadList();
522
523
524 /*
525 * Create a byte array to hold the data.
526 */
527 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
528 if (arrayObj != NULL)
529 memcpy(arrayObj->contents, tmpBuf, bufLen);
530 return arrayObj;
531}
532
533
534/*
535 * Find the specified thread and return its stack trace as an array of
536 * StackTraceElement objects.
537 */
538ArrayObject* dvmDdmGetStackTraceById(u4 threadId)
539{
540 Thread* self = dvmThreadSelf();
541 Thread* thread;
542 int* traceBuf;
543
544 dvmLockThreadList(self);
545
546 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
547 if (thread->threadId == threadId)
548 break;
549 }
550 if (thread == NULL) {
551 LOGI("dvmDdmGetStackTraceById: threadid=%d not found\n", threadId);
552 dvmUnlockThreadList();
553 return NULL;
554 }
555
556 /*
557 * Suspend the thread, pull out the stack trace, then resume the thread
558 * and release the thread list lock. If we're being asked to examine
559 * our own stack trace, skip the suspend/resume.
560 */
561 int stackDepth = -1;
562 if (thread != self)
563 dvmSuspendThread(thread);
564 traceBuf = dvmFillInStackTraceRaw(thread, &stackDepth);
565 if (thread != self)
566 dvmResumeThread(thread);
567 dvmUnlockThreadList();
568
569 /*
570 * Convert the raw buffer into an array of StackTraceElement.
571 */
572 ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
573 free(traceBuf);
574 return trace;
575}
576
577/*
578 * Gather up the allocation data and copy it into a byte[].
579 *
580 * Returns NULL on failure with an exception raised.
581 */
582ArrayObject* dvmDdmGetRecentAllocations(void)
583{
584 u1* data;
585 size_t len;
586
587 if (!dvmGenerateTrackedAllocationReport(&data, &len)) {
588 /* assume OOM */
589 dvmThrowException("Ljava/lang/OutOfMemoryError;","recent alloc native");
590 return NULL;
591 }
592
593 ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', len, ALLOC_DEFAULT);
594 if (arrayObj != NULL)
595 memcpy(arrayObj->contents, data, len);
596 return arrayObj;
597}
598