/* * Retrieve the native heap information and the info from /proc/self/maps, * copy them into a byte[] with a "struct Header" that holds data offsets, * and return the array. */ static jbyteArray DdmHandleNativeHeap_getLeakInfo(JNIEnv* env, jobject) { Header header; memset(&header, 0, sizeof(header)); String8 maps; ReadFile("/proc/self/maps", maps); header.mapSize = maps.size(); uint8_t* allocBytes; get_malloc_leak_info(&allocBytes, &header.allocSize, &header.allocInfoSize, &header.totalMemory, &header.backtraceSize); ALOGD("*** mapSize: %zu allocSize: %zu allocInfoSize: %zu totalMemory: %zu", header.mapSize, header.allocSize, header.allocInfoSize, header.totalMemory); #if defined(__LP64__) header.signature = DDMS_HEADER_SIGNATURE; header.version = DDMS_VERSION; header.pointerSize = sizeof(void*); #endif jbyteArray array = env->NewByteArray(sizeof(Header) + header.mapSize + header.allocSize); if (array != NULL) { env->SetByteArrayRegion(array, 0, sizeof(header), reinterpret_cast<jbyte*>(&header)); env->SetByteArrayRegion(array, sizeof(header), maps.size(), reinterpret_cast<const jbyte*>(maps.string())); env->SetByteArrayRegion(array, sizeof(header) + maps.size(), header.allocSize, reinterpret_cast<jbyte*>(allocBytes)); } free_malloc_leak_info(allocBytes); return array; }
/* * Retrieve the native heap information and the info from /proc/<self>/maps, * copy them into a byte[] with a "struct Header" that holds data offsets, * and return the array. */ static jbyteArray getLeakInfo(JNIEnv *env, jobject clazz) { #if defined(__arm__) // get the info in /proc/[pid]/map Header header; memset(&header, 0, sizeof(header)); pid_t pid = getpid(); char path[FILENAME_MAX]; sprintf(path, "/proc/%d/maps", pid); struct stat sb; int ret = stat(path, &sb); uint8_t* mapsFile = NULL; if (ret == 0) { mapsFile = (uint8_t*)malloc(MAPS_FILE_SIZE); int fd = open(path, O_RDONLY); if (mapsFile != NULL && fd != -1) { int amount = 0; do { uint8_t* ptr = mapsFile + header.mapSize; amount = read(fd, ptr, MAPS_FILE_SIZE); if (amount <= 0) { if (errno != EINTR) break; else continue; } header.mapSize += amount; } while (header.mapSize < MAPS_FILE_SIZE); LOGD("**** read %d bytes from '%s'", (int) header.mapSize, path); } } uint8_t* allocBytes; get_malloc_leak_info(&allocBytes, &header.allocSize, &header.allocInfoSize, &header.totalMemory, &header.backtraceSize); jbyte* bytes = NULL; jbyte* ptr = NULL; jbyteArray array = env->NewByteArray(sizeof(Header) + header.mapSize + header.allocSize); if (array == NULL) { goto done; } bytes = env->GetByteArrayElements(array, NULL); ptr = bytes; // LOGD("*** mapSize: %d allocSize: %d allocInfoSize: %d totalMemory: %d", // header.mapSize, header.allocSize, header.allocInfoSize, header.totalMemory); memcpy(ptr, &header, sizeof(header)); ptr += sizeof(header); if (header.mapSize > 0 && mapsFile != NULL) { memcpy(ptr, mapsFile, header.mapSize); ptr += header.mapSize; } memcpy(ptr, allocBytes, header.allocSize); env->ReleaseByteArrayElements(array, bytes, 0); done: if (mapsFile != NULL) { free(mapsFile); } // free the info up! free_malloc_leak_info(allocBytes); return array; #else return NULL; #endif }
void dumpMemoryAddresses(int fd) { const size_t SIZE = 256; char buffer[SIZE]; MyString8 result; typedef struct { size_t size; size_t dups; intptr_t * backtrace; } AllocEntry; uint8_t *info = NULL; size_t overallSize = 0; size_t infoSize = 0; size_t totalMemory = 0; size_t backtraceSize = 0; get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize); if (info) { uint8_t *ptr = info; size_t count = overallSize / infoSize; snprintf(buffer, SIZE, " Allocation count %i\n", count); result.append(buffer); snprintf(buffer, SIZE, " Total memory %i\n", totalMemory); result.append(buffer); AllocEntry * entries = new AllocEntry[count]; for (size_t i = 0; i < count; i++) { // Each entry should be size_t, size_t, intptr_t[backtraceSize] AllocEntry *e = &entries[i]; e->size = *reinterpret_cast<size_t *>(ptr); ptr += sizeof(size_t); e->dups = *reinterpret_cast<size_t *>(ptr); ptr += sizeof(size_t); e->backtrace = reinterpret_cast<intptr_t *>(ptr); ptr += sizeof(intptr_t) * backtraceSize; } // Now we need to sort the entries. They come sorted by size but // not by stack trace which causes problems using diff. bool moved; do { moved = false; for (size_t i = 0; i < (count - 1); i++) { AllocEntry *e1 = &entries[i]; AllocEntry *e2 = &entries[i+1]; bool swap = e1->size < e2->size; if (e1->size == e2->size) { for(size_t j = 0; j < backtraceSize; j++) { if (e1->backtrace[j] == e2->backtrace[j]) { continue; } swap = e1->backtrace[j] < e2->backtrace[j]; break; } } if (swap) { AllocEntry t = entries[i]; entries[i] = entries[i+1]; entries[i+1] = t; moved = true; } } } while (moved); write(fd, result.string(), result.size()); result.clear(); for (size_t i = 0; i < count; i++) { AllocEntry *e = &entries[i]; snprintf(buffer, SIZE, "size %8i, dup %4i, ", e->size, e->dups); result.append(buffer); for (size_t ct = 0; (ct < backtraceSize) && e->backtrace[ct]; ct++) { if (ct) { result.append(", "); } snprintf(buffer, SIZE, "0x%08x", e->backtrace[ct]); result.append(buffer); } result.append("\n"); write(fd, result.string(), result.size()); result.clear(); } delete[] entries; free_malloc_leak_info(info); } }
/* * The get_malloc_leak_info() call returns an array of structs that * look like this: * * size_t size * size_t allocations * intptr_t backtrace[32] * * "size" is the size of the allocation, "backtrace" is a fixed-size * array of function pointers, and "allocations" is the number of * allocations with the exact same size and backtrace. * * The entries are sorted by descending total size (i.e. size*allocations) * then allocation count. For best results with "diff" we'd like to sort * primarily by individual size then stack trace. Since the entries are * fixed-size, and we're allowed (by the current implementation) to mangle * them, we can do this in place. */ static void dumpNativeHeap(FILE* fp) { uint8_t* info = NULL; size_t overallSize, infoSize, totalMemory, backtraceSize; get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize); if (info == NULL) { fprintf(fp, "Native heap dump not available. To enable, run these" " commands (requires root):\n"); fprintf(fp, "$ adb shell setprop libc.debug.malloc 1\n"); fprintf(fp, "$ adb shell stop\n"); fprintf(fp, "$ adb shell start\n"); return; } assert(infoSize != 0); assert(overallSize % infoSize == 0); fprintf(fp, "Android Native Heap Dump v1.0\n\n"); size_t recordCount = overallSize / infoSize; fprintf(fp, "Total memory: %zu\n", totalMemory); fprintf(fp, "Allocation records: %zd\n", recordCount); if (backtraceSize != BACKTRACE_SIZE) { fprintf(fp, "WARNING: mismatched backtrace sizes (%d vs. %d)\n", backtraceSize, BACKTRACE_SIZE); } fprintf(fp, "\n"); /* re-sort the entries */ qsort(info, recordCount, infoSize, compareHeapRecords); /* dump the entries to the file */ const uint8_t* ptr = info; for (size_t idx = 0; idx < recordCount; idx++) { size_t size = *(size_t*) ptr; size_t allocations = *(size_t*) (ptr + sizeof(size_t)); intptr_t* backtrace = (intptr_t*) (ptr + sizeof(size_t) * 2); fprintf(fp, "z %d sz %8zu num %4zu bt", (size & SIZE_FLAG_ZYGOTE_CHILD) != 0, size & ~SIZE_FLAG_ZYGOTE_CHILD, allocations); for (size_t bt = 0; bt < backtraceSize; bt++) { if (backtrace[bt] == 0) { break; } else { fprintf(fp, " %08x", backtrace[bt]); } } fprintf(fp, "\n"); ptr += infoSize; } free_malloc_leak_info(info); fprintf(fp, "MAPS\n"); const char* maps = "/proc/self/maps"; FILE* in = fopen(maps, "r"); if (in == NULL) { fprintf(fp, "Could not open %s\n", maps); return; } char buf[BUFSIZ]; while (size_t n = fread(buf, sizeof(char), BUFSIZ, in)) { fwrite(buf, sizeof(char), n, fp); } fclose(in); fprintf(fp, "END\n"); }