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 * Allocation tracking and reporting. We maintain a circular buffer with
19 * the most recent allocations. The data can be viewed through DDMS.
20 *
21 * There are two basic approaches: manage the buffer with atomic updates
22 * and do a system-wide suspend when DDMS requests it, or protect all
23 * accesses with a mutex. The former is potentially more efficient, but
24 * the latter is much simpler and more reliable.
25 *
26 * Ideally we'd just use the object heap allocation mutex to guard this
27 * structure, but at the point we grab that (under dvmMalloc()) we're just
28 * allocating a collection of bytes and no longer have the class reference.
29 * Because this is an optional feature it's best to leave the existing
30 * code undisturbed and just use an additional lock.
31 *
32 * We don't currently track allocations of class objects. We could, but
33 * with the possible exception of Proxy objects they're not that interesting.
34 *
35 * TODO: if we add support for class unloading, we need to add the class
36 * references here to the root set (or just disable class unloading while
37 * this is active).
38 *
39 * TODO: consider making the parameters configurable, so DDMS can decide
40 * how many allocations it wants to see and what the stack depth should be.
41 * Changing the window size is easy, changing the max stack depth is harder
42 * because we go from an array of fixed-size structs to variable-sized data.
43 */
44 #include "Dalvik.h"
45
46 #define kMaxAllocRecordStackDepth 16 /* max 255 */
47 #define kNumAllocRecords 512 /* MUST be power of 2 */
48
49 /*
50 * Record the details of an allocation.
51 */
52 struct AllocRecord {
53 ClassObject* clazz; /* class allocated in this block */
54 u4 size; /* total size requested */
55 u2 threadId; /* simple thread ID; could be recycled */
56
57 /* stack trace elements; unused entries have method==NULL */
58 struct {
59 const Method* method; /* which method we're executing in */
60 int pc; /* current execution offset, in 16-bit units */
61 } stackElem[kMaxAllocRecordStackDepth];
62
63 /*
64 * This was going to be either wall-clock time in seconds or monotonic
65 * time in milliseconds since the VM started, to give a rough sense for
66 * how long ago an allocation happened. This adds a system call per
67 * allocation, which is too much overhead.
68 */
69 //u4 timestamp;
70 };
71
72 /*
73 * Initialize a few things. This gets called early, so keep activity to
74 * a minimum.
75 */
dvmAllocTrackerStartup(void)76 bool dvmAllocTrackerStartup(void)
77 {
78 /* prep locks */
79 dvmInitMutex(&gDvm.allocTrackerLock);
80
81 /* initialized when enabled by DDMS */
82 assert(gDvm.allocRecords == NULL);
83
84 return true;
85 }
86
87 /*
88 * Release anything we're holding on to.
89 */
dvmAllocTrackerShutdown(void)90 void dvmAllocTrackerShutdown(void)
91 {
92 free(gDvm.allocRecords);
93 dvmDestroyMutex(&gDvm.allocTrackerLock);
94 }
95
96
97 /*
98 * ===========================================================================
99 * Collection
100 * ===========================================================================
101 */
102
103 /*
104 * Enable allocation tracking. Does nothing if tracking is already enabled.
105 *
106 * Returns "true" on success.
107 */
dvmEnableAllocTracker(void)108 bool dvmEnableAllocTracker(void)
109 {
110 bool result = true;
111 dvmLockMutex(&gDvm.allocTrackerLock);
112
113 if (gDvm.allocRecords == NULL) {
114 LOGI("Enabling alloc tracker (%d entries, %d frames --> %d bytes)\n",
115 kNumAllocRecords, kMaxAllocRecordStackDepth,
116 sizeof(AllocRecord) * kNumAllocRecords);
117 gDvm.allocRecordHead = gDvm.allocRecordCount = 0;
118 gDvm.allocRecords =
119 (AllocRecord*) malloc(sizeof(AllocRecord) * kNumAllocRecords);
120
121 if (gDvm.allocRecords == NULL)
122 result = false;
123 }
124
125 dvmUnlockMutex(&gDvm.allocTrackerLock);
126 return result;
127 }
128
129 /*
130 * Disable allocation tracking. Does nothing if tracking is not enabled.
131 */
dvmDisableAllocTracker(void)132 void dvmDisableAllocTracker(void)
133 {
134 dvmLockMutex(&gDvm.allocTrackerLock);
135
136 if (gDvm.allocRecords != NULL) {
137 free(gDvm.allocRecords);
138 gDvm.allocRecords = NULL;
139 }
140
141 dvmUnlockMutex(&gDvm.allocTrackerLock);
142 }
143
144 /*
145 * Get the last few stack frames.
146 */
getStackFrames(Thread * self,AllocRecord * pRec)147 static void getStackFrames(Thread* self, AllocRecord* pRec)
148 {
149 int stackDepth = 0;
150 void* fp;
151
152 fp = self->curFrame;
153
154 while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
155 const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
156 const Method* method = saveArea->method;
157
158 if (!dvmIsBreakFrame(fp)) {
159 pRec->stackElem[stackDepth].method = method;
160 if (dvmIsNativeMethod(method)) {
161 pRec->stackElem[stackDepth].pc = 0;
162 } else {
163 assert(saveArea->xtra.currentPc >= method->insns &&
164 saveArea->xtra.currentPc <
165 method->insns + dvmGetMethodInsnsSize(method));
166 pRec->stackElem[stackDepth].pc =
167 (int) (saveArea->xtra.currentPc - method->insns);
168 }
169 stackDepth++;
170 }
171
172 assert(fp != saveArea->prevFrame);
173 fp = saveArea->prevFrame;
174 }
175
176 /* clear out the rest (normally there won't be any) */
177 while (stackDepth < kMaxAllocRecordStackDepth) {
178 pRec->stackElem[stackDepth].method = NULL;
179 pRec->stackElem[stackDepth].pc = 0;
180 stackDepth++;
181 }
182 }
183
184 /*
185 * Add a new allocation to the set.
186 */
dvmDoTrackAllocation(ClassObject * clazz,int size)187 void dvmDoTrackAllocation(ClassObject* clazz, int size)
188 {
189 dvmLockMutex(&gDvm.allocTrackerLock);
190 if (gDvm.allocRecords == NULL)
191 goto bail;
192
193 Thread* self = dvmThreadSelf();
194 if (self == NULL) {
195 LOGW("alloc tracker: no thread\n");
196 goto bail;
197 }
198
199 /* advance and clip */
200 if (++gDvm.allocRecordHead == kNumAllocRecords)
201 gDvm.allocRecordHead = 0;
202
203 AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
204
205 pRec->clazz = clazz;
206 pRec->size = size;
207 pRec->threadId = self->threadId;
208 getStackFrames(self, pRec);
209
210 if (gDvm.allocRecordCount < kNumAllocRecords)
211 gDvm.allocRecordCount++;
212
213 bail:
214 dvmUnlockMutex(&gDvm.allocTrackerLock);
215 }
216
217
218 /*
219 * ===========================================================================
220 * Reporting
221 * ===========================================================================
222 */
223
224 /*
225 The data we send to DDMS contains everything we have recorded.
226
227 Message header (all values big-endian):
228 (1b) message header len (to allow future expansion); includes itself
229 (1b) entry header len
230 (1b) stack frame len
231 (2b) number of entries
232 (4b) offset to string table from start of message
233 (2b) number of class name strings
234 (2b) number of method name strings
235 (2b) number of source file name strings
236 For each entry:
237 (4b) total allocation size
238 (2b) threadId
239 (2b) allocated object's class name index
240 (1b) stack depth
241 For each stack frame:
242 (2b) method's class name
243 (2b) method name
244 (2b) method source file
245 (2b) line number, clipped to 32767; -2 if native; -1 if no source
246 (xb) class name strings
247 (xb) method name strings
248 (xb) source file strings
249
250 As with other DDM traffic, strings are sent as a 4-byte length
251 followed by UTF-16 data.
252
253 We send up 16-bit unsigned indexes into string tables. In theory there
254 can be (kMaxAllocRecordStackDepth * kNumAllocRecords) unique strings in
255 each table, but in practice there should be far fewer.
256
257 The chief reason for using a string table here is to keep the size of
258 the DDMS message to a minimum. This is partly to make the protocol
259 efficient, but also because we have to form the whole thing up all at
260 once in a memory buffer.
261
262 We use separate string tables for class names, method names, and source
263 files to keep the indexes small. There will generally be no overlap
264 between the contents of these tables.
265 */
266 const int kMessageHeaderLen = 15;
267 const int kEntryHeaderLen = 9;
268 const int kStackFrameLen = 8;
269
270 /*
271 * Return the index of the head element.
272 *
273 * We point at the most-recently-written record, so if allocRecordCount is 1
274 * we want to use the current element. Take "head+1" and subtract count
275 * from it.
276 *
277 * We need to handle underflow in our circular buffer, so we add
278 * kNumAllocRecords and then mask it back down.
279 */
headIndex(void)280 inline static int headIndex(void)
281 {
282 return (gDvm.allocRecordHead+1 + kNumAllocRecords - gDvm.allocRecordCount)
283 & (kNumAllocRecords-1);
284 }
285
286 /*
287 * Dump the contents of a PointerSet full of character pointers.
288 */
dumpStringTable(PointerSet * strings)289 static void dumpStringTable(PointerSet* strings)
290 {
291 int count = dvmPointerSetGetCount(strings);
292 int i;
293
294 for (i = 0; i < count; i++)
295 printf(" %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
296 }
297
298 /*
299 * Get the method's source file. If we don't know it, return "" instead
300 * of a NULL pointer.
301 */
getMethodSourceFile(const Method * method)302 static const char* getMethodSourceFile(const Method* method)
303 {
304 const char* fileName = dvmGetMethodSourceFile(method);
305 if (fileName == NULL)
306 fileName = "";
307 return fileName;
308 }
309
310 /*
311 * Generate string tables.
312 *
313 * Our source material is UTF-8 string constants from DEX files. If we
314 * want to be thorough we can generate a hash value for each string and
315 * use the VM hash table implementation, or we can do a quick & dirty job
316 * by just maintaining a list of unique pointers. If the same string
317 * constant appears in multiple DEX files we'll end up with duplicates,
318 * but in practice this shouldn't matter (and if it does, we can uniq-sort
319 * the result in a second pass).
320 */
populateStringTables(PointerSet * classNames,PointerSet * methodNames,PointerSet * fileNames)321 static bool populateStringTables(PointerSet* classNames,
322 PointerSet* methodNames, PointerSet* fileNames)
323 {
324 int count = gDvm.allocRecordCount;
325 int idx = headIndex();
326 int classCount, methodCount, fileCount; /* debug stats */
327
328 classCount = methodCount = fileCount = 0;
329
330 while (count--) {
331 AllocRecord* pRec = &gDvm.allocRecords[idx];
332
333 dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
334 classCount++;
335
336 int i;
337 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
338 if (pRec->stackElem[i].method == NULL)
339 break;
340
341 const Method* method = pRec->stackElem[i].method;
342 dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
343 classCount++;
344 dvmPointerSetAddEntry(methodNames, method->name);
345 methodCount++;
346 dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
347 fileCount++;
348 }
349
350 idx = (idx + 1) & (kNumAllocRecords-1);
351 }
352
353 LOGI("class %d/%d, method %d/%d, file %d/%d\n",
354 dvmPointerSetGetCount(classNames), classCount,
355 dvmPointerSetGetCount(methodNames), methodCount,
356 dvmPointerSetGetCount(fileNames), fileCount);
357
358 return true;
359 }
360
361 /*
362 * Generate the base info (i.e. everything but the string tables).
363 *
364 * This should be called twice. On the first call, "ptr" is NULL and
365 * "baseLen" is zero. The return value is used to allocate a buffer.
366 * On the second call, "ptr" points to a data buffer, and "baseLen"
367 * holds the value from the result of the first call.
368 *
369 * The size of the output data is returned.
370 */
generateBaseOutput(u1 * ptr,size_t baseLen,const PointerSet * classNames,const PointerSet * methodNames,const PointerSet * fileNames)371 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
372 const PointerSet* classNames, const PointerSet* methodNames,
373 const PointerSet* fileNames)
374 {
375 u1* origPtr = ptr;
376 int count = gDvm.allocRecordCount;
377 int idx = headIndex();
378
379 if (origPtr != NULL) {
380 set1(&ptr[0], kMessageHeaderLen);
381 set1(&ptr[1], kEntryHeaderLen);
382 set1(&ptr[2], kStackFrameLen);
383 set2BE(&ptr[3], count);
384 set4BE(&ptr[5], baseLen);
385 set2BE(&ptr[9], dvmPointerSetGetCount(classNames));
386 set2BE(&ptr[11], dvmPointerSetGetCount(methodNames));
387 set2BE(&ptr[13], dvmPointerSetGetCount(fileNames));
388 }
389 ptr += kMessageHeaderLen;
390
391 while (count--) {
392 AllocRecord* pRec = &gDvm.allocRecords[idx];
393
394 /* compute depth */
395 int depth;
396 for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
397 if (pRec->stackElem[depth].method == NULL)
398 break;
399 }
400
401 /* output header */
402 if (origPtr != NULL) {
403 set4BE(&ptr[0], pRec->size);
404 set2BE(&ptr[4], pRec->threadId);
405 set2BE(&ptr[6],
406 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
407 set1(&ptr[8], depth);
408 }
409 ptr += kEntryHeaderLen;
410
411 /* convert stack frames */
412 int i;
413 for (i = 0; i < depth; i++) {
414 if (origPtr != NULL) {
415 const Method* method = pRec->stackElem[i].method;
416 int lineNum;
417
418 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
419 if (lineNum > 32767)
420 lineNum = 32767;
421
422 set2BE(&ptr[0], dvmPointerSetFind(classNames,
423 method->clazz->descriptor));
424 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
425 method->name));
426 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
427 getMethodSourceFile(method)));
428 set2BE(&ptr[6], (u2)lineNum);
429 }
430 ptr += kStackFrameLen;
431 }
432
433 idx = (idx + 1) & (kNumAllocRecords-1);
434 }
435
436 return ptr - origPtr;
437 }
438
439 /*
440 * Compute the size required to store a string table. Includes the length
441 * word and conversion to UTF-16.
442 */
computeStringTableSize(PointerSet * strings)443 static size_t computeStringTableSize(PointerSet* strings)
444 {
445 int count = dvmPointerSetGetCount(strings);
446 size_t size = 0;
447 int i;
448
449 for (i = 0; i < count; i++) {
450 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
451
452 size += 4 + dvmUtf8Len(str) * 2;
453 }
454
455 return size;
456 }
457
458 /*
459 * Convert a UTF-8 string to UTF-16. We also need to byte-swap the values
460 * to big-endian, and we can't assume even alignment on the target.
461 *
462 * Returns the string's length, in characters.
463 */
convertUtf8ToUtf16BEUA(u1 * utf16Str,const char * utf8Str)464 int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
465 {
466 u1* origUtf16Str = utf16Str;
467
468 while (*utf8Str != '\0') {
469 u2 utf16 = dexGetUtf16FromUtf8(&utf8Str); /* advances utf8Str */
470 set2BE(utf16Str, utf16);
471 utf16Str += 2;
472 }
473
474 return (utf16Str - origUtf16Str) / 2;
475 }
476
477 /*
478 * Output a string table serially.
479 */
outputStringTable(PointerSet * strings,u1 * ptr)480 static size_t outputStringTable(PointerSet* strings, u1* ptr)
481 {
482 int count = dvmPointerSetGetCount(strings);
483 u1* origPtr = ptr;
484 int i;
485
486 for (i = 0; i < count; i++) {
487 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
488 int charLen;
489
490 /* copy UTF-8 string to big-endian unaligned UTF-16 */
491 charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
492 set4BE(&ptr[0], charLen);
493
494 ptr += 4 + charLen * 2;
495 }
496
497 return ptr - origPtr;
498 }
499
500 /*
501 * Generate a DDM packet with all of the tracked allocation data.
502 *
503 * On success, returns "true" with "*pData" and "*pDataLen" set.
504 */
dvmGenerateTrackedAllocationReport(u1 ** pData,size_t * pDataLen)505 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
506 {
507 bool result = false;
508 u1* buffer = NULL;
509
510 dvmLockMutex(&gDvm.allocTrackerLock);
511
512 /*
513 * Part 1: generate string tables.
514 */
515 PointerSet* classNames = NULL;
516 PointerSet* methodNames = NULL;
517 PointerSet* fileNames = NULL;
518
519 /*
520 * Allocate storage. Usually there's 60-120 of each thing (sampled
521 * when max=512), but it varies widely and isn't closely bound to
522 * the number of allocations we've captured. The sets expand quickly
523 * if needed.
524 */
525 classNames = dvmPointerSetAlloc(128);
526 methodNames = dvmPointerSetAlloc(128);
527 fileNames = dvmPointerSetAlloc(128);
528 if (classNames == NULL || methodNames == NULL || fileNames == NULL) {
529 LOGE("Failed allocating pointer sets\n");
530 goto bail;
531 }
532
533 if (!populateStringTables(classNames, methodNames, fileNames))
534 goto bail;
535
536 if (false) {
537 printf("Classes:\n");
538 dumpStringTable(classNames);
539 printf("Methods:\n");
540 dumpStringTable(methodNames);
541 printf("Files:\n");
542 dumpStringTable(fileNames);
543 }
544
545 /*
546 * Part 2: compute the size of the output.
547 *
548 * (Could also just write to an expanding buffer.)
549 */
550 size_t baseSize, totalSize;
551 baseSize = generateBaseOutput(NULL, 0, classNames, methodNames, fileNames);
552 assert(baseSize > 0);
553 totalSize = baseSize;
554 totalSize += computeStringTableSize(classNames);
555 totalSize += computeStringTableSize(methodNames);
556 totalSize += computeStringTableSize(fileNames);
557 LOGI("Generated AT, size is %zd/%zd\n", baseSize, totalSize);
558
559 /*
560 * Part 3: allocate a buffer and generate the output.
561 */
562 u1* strPtr;
563
564 buffer = (u1*) malloc(totalSize);
565 strPtr = buffer + baseSize;
566 generateBaseOutput(buffer, baseSize, classNames, methodNames, fileNames);
567 strPtr += outputStringTable(classNames, strPtr);
568 strPtr += outputStringTable(methodNames, strPtr);
569 strPtr += outputStringTable(fileNames, strPtr);
570 if (strPtr - buffer != (int)totalSize) {
571 LOGE("size mismatch (%d vs %zd)\n", strPtr - buffer, totalSize);
572 dvmAbort();
573 }
574 //dvmPrintHexDump(buffer, totalSize);
575
576 *pData = buffer;
577 *pDataLen = totalSize;
578 buffer = NULL; // don't free -- caller will own
579 result = true;
580
581 bail:
582 dvmPointerSetFree(classNames);
583 dvmPointerSetFree(methodNames);
584 dvmPointerSetFree(fileNames);
585 free(buffer);
586 dvmUnlockMutex(&gDvm.allocTrackerLock);
587 //dvmDumpTrackedAllocations(false);
588 return result;
589 }
590
591 /*
592 * Dump the tracked allocations to the log file.
593 *
594 * If "enable" is set, we try to enable the feature if it's not already
595 * active.
596 */
dvmDumpTrackedAllocations(bool enable)597 void dvmDumpTrackedAllocations(bool enable)
598 {
599 if (enable)
600 dvmEnableAllocTracker();
601
602 dvmLockMutex(&gDvm.allocTrackerLock);
603 if (gDvm.allocRecords == NULL)
604 goto bail;
605
606 /*
607 * "idx" is the head of the list. We want to start at the end of the
608 * list and move forward to the tail.
609 */
610 int idx = headIndex();
611 int count = gDvm.allocRecordCount;
612
613 LOGI("Tracked allocations, (head=%d count=%d)\n",
614 gDvm.allocRecordHead, count);
615 while (count--) {
616 AllocRecord* pRec = &gDvm.allocRecords[idx];
617 LOGI(" T=%-2d %6d %s\n",
618 pRec->threadId, pRec->size, pRec->clazz->descriptor);
619
620 if (true) {
621 int i;
622 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
623 if (pRec->stackElem[i].method == NULL)
624 break;
625
626 const Method* method = pRec->stackElem[i].method;
627 if (dvmIsNativeMethod(method)) {
628 LOGI(" %s.%s (Native)\n",
629 method->clazz->descriptor, method->name);
630 } else {
631 LOGI(" %s.%s +%d\n",
632 method->clazz->descriptor, method->name,
633 pRec->stackElem[i].pc);
634 }
635 }
636 }
637
638 /* pause periodically to help logcat catch up */
639 if ((count % 5) == 0)
640 usleep(40000);
641
642 idx = (idx + 1) & (kNumAllocRecords-1);
643 }
644
645 bail:
646 dvmUnlockMutex(&gDvm.allocTrackerLock);
647 if (false) {
648 u1* data;
649 size_t dataLen;
650 if (dvmGenerateTrackedAllocationReport(&data, &dataLen))
651 free(data);
652 }
653 }
654