Implement native dump for "am dumpheap -n".
This adds the ability to generate a trivial heap dump on demand. If
the appropriate system properties aren't set, the output file will
instead contain instructions for enabling them.
The data returned by get_malloc_leak_info() is processed and written
to the specified file. The output looks something like:
Android Native Heap Dump v1.0
Total memory: 2981301
Allocation records: 2152
z 1 sz 65557 num 1 bt 8010dd78 afd12618 ...
z 1 sz 52008 num 3 bt 8010dd78 afd12618 ...
z 1 sz 24428 num 1 bt 8010dd78 8010de84 ...
...2149 more...
END
(the "..." is actually the remaining entries in the stack backtrace;
I truncated it here)
"z" indicates whether the allocation was made pre- or post-zygote,
"sz" is the size allocated, and "num" is the number of allocations
made of that size with the specified backtrace.
Change-Id: I2d60f07444fce5f7178b3f51c928c8faa0b051bd
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 3ee404a..4a877d2 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#define LOG_TAG "android.os.Debug"
#include "JNIHelp.h"
#include "jni.h"
#include "utils/misc.h"
@@ -24,6 +25,8 @@
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
+#include <errno.h>
+#include <assert.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
@@ -274,6 +277,176 @@
jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz);
jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz);
+
+#ifdef HAVE_ANDROID_OS
+/* pulled out of bionic */
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
+ size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
+extern "C" void free_malloc_leak_info(uint8_t* info);
+#define SIZE_FLAG_ZYGOTE_CHILD (1<<31)
+#define BACKTRACE_SIZE 32
+
+/*
+ * This is a qsort() callback.
+ *
+ * See dumpNativeHeap() for comments about the data format and sort order.
+ */
+static int compareHeapRecords(const void* vrec1, const void* vrec2)
+{
+ const size_t* rec1 = (const size_t*) vrec1;
+ const size_t* rec2 = (const size_t*) vrec2;
+ size_t size1 = *rec1;
+ size_t size2 = *rec2;
+
+ if (size1 < size2) {
+ return 1;
+ } else if (size1 > size2) {
+ return -1;
+ }
+
+ intptr_t* bt1 = (intptr_t*)(rec1 + 2);
+ intptr_t* bt2 = (intptr_t*)(rec2 + 2);
+ for (size_t idx = 0; idx < BACKTRACE_SIZE; idx++) {
+ intptr_t addr1 = bt1[idx];
+ intptr_t addr2 = bt2[idx];
+ if (addr1 == addr2) {
+ if (addr1 == 0)
+ break;
+ continue;
+ }
+ if (addr1 < addr2) {
+ return -1;
+ } else if (addr1 > addr2) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * 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;
+ }
+
+ fprintf(fp, "END\n");
+ free_malloc_leak_info(info);
+}
+#endif /*HAVE_ANDROID_OS*/
+
+/*
+ * Dump the native heap, writing human-readable output to the specified
+ * file descriptor.
+ */
+static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject clazz,
+ jobject fileDescriptor)
+{
+ if (fileDescriptor == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+ int origFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ if (origFd < 0) {
+ jniThrowRuntimeException(env, "Invalid file descriptor");
+ return;
+ }
+
+ /* dup() the descriptor so we don't close the original with fclose() */
+ int fd = dup(origFd);
+ if (fd < 0) {
+ LOGW("dup(%d) failed: %s\n", origFd, strerror(errno));
+ jniThrowRuntimeException(env, "dup() failed");
+ return;
+ }
+
+ FILE* fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ LOGW("fdopen(%d) failed: %s\n", fd, strerror(errno));
+ close(fd);
+ jniThrowRuntimeException(env, "fdopen() failed");
+ return;
+ }
+
+#ifdef HAVE_ANDROID_OS
+ LOGD("Native heap dump starting...\n");
+ dumpNativeHeap(fp);
+ LOGD("Native heap dump complete.\n");
+#else
+ fprintf(fp, "Native heap dump not available on this platform\n");
+#endif
+
+ fclose(fp);
+}
+
+
/*
* JNI registration.
*/
@@ -289,6 +462,8 @@
(void*) android_os_Debug_getDirtyPages },
{ "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)V",
(void*) android_os_Debug_getDirtyPagesPid },
+ { "dumpNativeHeap", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_os_Debug_dumpNativeHeap },
{ "getBinderSentTransactions", "()I",
(void*) android_os_Debug_getBinderSentTransactions },
{ "getBinderReceivedTransactions", "()I",
@@ -320,4 +495,4 @@
return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));
}
-};
+}; // namespace android