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