1/*
2 * Copyright (C) 2007 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#define LOG_TAG "android.os.Debug"
18#include "JNIHelp.h"
19#include "jni.h"
20#include <utils/String8.h>
21#include "utils/misc.h"
22#include "cutils/debugger.h"
23
24#include <fcntl.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29#include <time.h>
30#include <sys/time.h>
31#include <errno.h>
32#include <assert.h>
33#include <ctype.h>
34
35#ifdef HAVE_MALLOC_H
36#include <malloc.h>
37#endif
38
39namespace android
40{
41
42enum {
43    HEAP_UNKNOWN,
44    HEAP_DALVIK,
45    HEAP_NATIVE,
46    HEAP_CURSOR,
47    HEAP_ASHMEM,
48    HEAP_UNKNOWN_DEV,
49    HEAP_SO,
50    HEAP_JAR,
51    HEAP_APK,
52    HEAP_TTF,
53    HEAP_DEX,
54    HEAP_UNKNOWN_MAP,
55
56    _NUM_HEAP,
57    _NUM_CORE_HEAP = HEAP_NATIVE+1
58};
59
60struct stat_fields {
61    jfieldID pss_field;
62    jfieldID privateDirty_field;
63    jfieldID sharedDirty_field;
64};
65
66struct stat_field_names {
67    const char* pss_name;
68    const char* privateDirty_name;
69    const char* sharedDirty_name;
70};
71
72static stat_fields stat_fields[_NUM_CORE_HEAP];
73
74static stat_field_names stat_field_names[_NUM_CORE_HEAP] = {
75    { "otherPss", "otherPrivateDirty", "otherSharedDirty" },
76    { "dalvikPss", "dalvikPrivateDirty", "dalvikSharedDirty" },
77    { "nativePss", "nativePrivateDirty", "nativeSharedDirty" }
78};
79
80jfieldID otherStats_field;
81
82struct stats_t {
83    int pss;
84    int privateDirty;
85    int sharedDirty;
86};
87
88#define BINDER_STATS "/proc/binder/stats"
89
90static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz)
91{
92#ifdef HAVE_MALLOC_H
93    struct mallinfo info = mallinfo();
94    return (jlong) info.usmblks;
95#else
96    return -1;
97#endif
98}
99
100static jlong android_os_Debug_getNativeHeapAllocatedSize(JNIEnv *env, jobject clazz)
101{
102#ifdef HAVE_MALLOC_H
103    struct mallinfo info = mallinfo();
104    return (jlong) info.uordblks;
105#else
106    return -1;
107#endif
108}
109
110static jlong android_os_Debug_getNativeHeapFreeSize(JNIEnv *env, jobject clazz)
111{
112#ifdef HAVE_MALLOC_H
113    struct mallinfo info = mallinfo();
114    return (jlong) info.fordblks;
115#else
116    return -1;
117#endif
118}
119
120static void read_mapinfo(FILE *fp, stats_t* stats)
121{
122    char line[1024];
123    int len, nameLen;
124    bool skip, done = false;
125
126    unsigned size = 0, resident = 0, pss = 0;
127    unsigned shared_clean = 0, shared_dirty = 0;
128    unsigned private_clean = 0, private_dirty = 0;
129    unsigned referenced = 0;
130    unsigned temp;
131
132    unsigned long int start;
133    unsigned long int end = 0;
134    unsigned long int prevEnd = 0;
135    char* name;
136    int name_pos;
137
138    int whichHeap = HEAP_UNKNOWN;
139    int prevHeap = HEAP_UNKNOWN;
140
141    if(fgets(line, sizeof(line), fp) == 0) return;
142
143    while (!done) {
144        prevHeap = whichHeap;
145        prevEnd = end;
146        whichHeap = HEAP_UNKNOWN;
147        skip = false;
148
149        len = strlen(line);
150        if (len < 1) return;
151        line[--len] = 0;
152
153        if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
154            skip = true;
155        } else {
156            while (isspace(line[name_pos])) {
157                name_pos += 1;
158            }
159            name = line + name_pos;
160            nameLen = strlen(name);
161
162            if (strstr(name, "[heap]") == name) {
163                whichHeap = HEAP_NATIVE;
164            } else if (strstr(name, "/dev/ashmem/dalvik-") == name) {
165                whichHeap = HEAP_DALVIK;
166            } else if (strstr(name, "/dev/ashmem/CursorWindow") == name) {
167                whichHeap = HEAP_CURSOR;
168            } else if (strstr(name, "/dev/ashmem/") == name) {
169                whichHeap = HEAP_ASHMEM;
170            } else if (strstr(name, "/dev/") == name) {
171                whichHeap = HEAP_UNKNOWN_DEV;
172            } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
173                whichHeap = HEAP_SO;
174            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
175                whichHeap = HEAP_JAR;
176            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
177                whichHeap = HEAP_APK;
178            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
179                whichHeap = HEAP_TTF;
180            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".dex") == 0) {
181                whichHeap = HEAP_DEX;
182            } else if (nameLen > 0) {
183                whichHeap = HEAP_UNKNOWN_MAP;
184            } else if (start == prevEnd && prevHeap == HEAP_SO) {
185                // bss section of a shared library.
186                whichHeap = HEAP_SO;
187            }
188        }
189
190        //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
191        //    isSqliteHeap, line);
192
193        while (true) {
194            if (fgets(line, 1024, fp) == 0) {
195                done = true;
196                break;
197            }
198
199            if (sscanf(line, "Size: %d kB", &temp) == 1) {
200                size = temp;
201            } else if (sscanf(line, "Rss: %d kB", &temp) == 1) {
202                resident = temp;
203            } else if (sscanf(line, "Pss: %d kB", &temp) == 1) {
204                pss = temp;
205            } else if (sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
206                shared_clean = temp;
207            } else if (sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
208                shared_dirty = temp;
209            } else if (sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
210                private_clean = temp;
211            } else if (sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
212                private_dirty = temp;
213            } else if (sscanf(line, "Referenced: %d kB", &temp) == 1) {
214                referenced = temp;
215            } else if (strlen(line) > 30 && line[8] == '-' && line[17] == ' ') {
216                // looks like a new mapping
217                // example: "10000000-10001000 ---p 10000000 00:00 0"
218                break;
219            }
220        }
221
222        if (!skip) {
223            stats[whichHeap].pss += pss;
224            stats[whichHeap].privateDirty += private_dirty;
225            stats[whichHeap].sharedDirty += shared_dirty;
226        }
227    }
228}
229
230static void load_maps(int pid, stats_t* stats)
231{
232    char tmp[128];
233    FILE *fp;
234
235    sprintf(tmp, "/proc/%d/smaps", pid);
236    fp = fopen(tmp, "r");
237    if (fp == 0) return;
238
239    read_mapinfo(fp, stats);
240    fclose(fp);
241}
242
243static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
244        jint pid, jobject object)
245{
246    stats_t stats[_NUM_HEAP];
247    memset(&stats, 0, sizeof(stats));
248
249    load_maps(pid, stats);
250
251    for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) {
252        stats[HEAP_UNKNOWN].pss += stats[i].pss;
253        stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty;
254        stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty;
255    }
256
257    for (int i=0; i<_NUM_CORE_HEAP; i++) {
258        env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss);
259        env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty);
260        env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty);
261    }
262
263    jintArray otherIntArray = (jintArray)env->GetObjectField(object, otherStats_field);
264
265    jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0);
266    if (otherArray == NULL) {
267        return;
268    }
269
270    int j=0;
271    for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) {
272        otherArray[j++] = stats[i].pss;
273        otherArray[j++] = stats[i].privateDirty;
274        otherArray[j++] = stats[i].sharedDirty;
275    }
276
277    env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);
278}
279
280static void android_os_Debug_getDirtyPages(JNIEnv *env, jobject clazz, jobject object)
281{
282    android_os_Debug_getDirtyPagesPid(env, clazz, getpid(), object);
283}
284
285static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid)
286{
287    char line[1024];
288    jlong pss = 0;
289    unsigned temp;
290
291    char tmp[128];
292    FILE *fp;
293
294    sprintf(tmp, "/proc/%d/smaps", pid);
295    fp = fopen(tmp, "r");
296    if (fp == 0) return 0;
297
298    while (true) {
299        if (fgets(line, 1024, fp) == 0) {
300            break;
301        }
302
303        if (sscanf(line, "Pss: %d kB", &temp) == 1) {
304            pss += temp;
305        }
306    }
307
308    fclose(fp);
309
310    return pss;
311}
312
313static jlong android_os_Debug_getPss(JNIEnv *env, jobject clazz)
314{
315    return android_os_Debug_getPssPid(env, clazz, getpid());
316}
317
318static jint read_binder_stat(const char* stat)
319{
320    FILE* fp = fopen(BINDER_STATS, "r");
321    if (fp == NULL) {
322        return -1;
323    }
324
325    char line[1024];
326
327    char compare[128];
328    int len = snprintf(compare, 128, "proc %d", getpid());
329
330    // loop until we have the block that represents this process
331    do {
332        if (fgets(line, 1024, fp) == 0) {
333            return -1;
334        }
335    } while (strncmp(compare, line, len));
336
337    // now that we have this process, read until we find the stat that we are looking for
338    len = snprintf(compare, 128, "  %s: ", stat);
339
340    do {
341        if (fgets(line, 1024, fp) == 0) {
342            return -1;
343        }
344    } while (strncmp(compare, line, len));
345
346    // we have the line, now increment the line ptr to the value
347    char* ptr = line + len;
348    return atoi(ptr);
349}
350
351static jint android_os_Debug_getBinderSentTransactions(JNIEnv *env, jobject clazz)
352{
353    return read_binder_stat("bcTRANSACTION");
354}
355
356static jint android_os_getBinderReceivedTransactions(JNIEnv *env, jobject clazz)
357{
358    return read_binder_stat("brTRANSACTION");
359}
360
361// these are implemented in android_util_Binder.cpp
362jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz);
363jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz);
364jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz);
365
366
367/* pulled out of bionic */
368extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
369    size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
370extern "C" void free_malloc_leak_info(uint8_t* info);
371#define SIZE_FLAG_ZYGOTE_CHILD  (1<<31)
372#define BACKTRACE_SIZE          32
373
374/*
375 * This is a qsort() callback.
376 *
377 * See dumpNativeHeap() for comments about the data format and sort order.
378 */
379static int compareHeapRecords(const void* vrec1, const void* vrec2)
380{
381    const size_t* rec1 = (const size_t*) vrec1;
382    const size_t* rec2 = (const size_t*) vrec2;
383    size_t size1 = *rec1;
384    size_t size2 = *rec2;
385
386    if (size1 < size2) {
387        return 1;
388    } else if (size1 > size2) {
389        return -1;
390    }
391
392    intptr_t* bt1 = (intptr_t*)(rec1 + 2);
393    intptr_t* bt2 = (intptr_t*)(rec2 + 2);
394    for (size_t idx = 0; idx < BACKTRACE_SIZE; idx++) {
395        intptr_t addr1 = bt1[idx];
396        intptr_t addr2 = bt2[idx];
397        if (addr1 == addr2) {
398            if (addr1 == 0)
399                break;
400            continue;
401        }
402        if (addr1 < addr2) {
403            return -1;
404        } else if (addr1 > addr2) {
405            return 1;
406        }
407    }
408
409    return 0;
410}
411
412/*
413 * The get_malloc_leak_info() call returns an array of structs that
414 * look like this:
415 *
416 *   size_t size
417 *   size_t allocations
418 *   intptr_t backtrace[32]
419 *
420 * "size" is the size of the allocation, "backtrace" is a fixed-size
421 * array of function pointers, and "allocations" is the number of
422 * allocations with the exact same size and backtrace.
423 *
424 * The entries are sorted by descending total size (i.e. size*allocations)
425 * then allocation count.  For best results with "diff" we'd like to sort
426 * primarily by individual size then stack trace.  Since the entries are
427 * fixed-size, and we're allowed (by the current implementation) to mangle
428 * them, we can do this in place.
429 */
430static void dumpNativeHeap(FILE* fp)
431{
432    uint8_t* info = NULL;
433    size_t overallSize, infoSize, totalMemory, backtraceSize;
434
435    get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory,
436        &backtraceSize);
437    if (info == NULL) {
438        fprintf(fp, "Native heap dump not available. To enable, run these"
439                    " commands (requires root):\n");
440        fprintf(fp, "$ adb shell setprop libc.debug.malloc 1\n");
441        fprintf(fp, "$ adb shell stop\n");
442        fprintf(fp, "$ adb shell start\n");
443        return;
444    }
445    assert(infoSize != 0);
446    assert(overallSize % infoSize == 0);
447
448    fprintf(fp, "Android Native Heap Dump v1.0\n\n");
449
450    size_t recordCount = overallSize / infoSize;
451    fprintf(fp, "Total memory: %zu\n", totalMemory);
452    fprintf(fp, "Allocation records: %zd\n", recordCount);
453    if (backtraceSize != BACKTRACE_SIZE) {
454        fprintf(fp, "WARNING: mismatched backtrace sizes (%d vs. %d)\n",
455            backtraceSize, BACKTRACE_SIZE);
456    }
457    fprintf(fp, "\n");
458
459    /* re-sort the entries */
460    qsort(info, recordCount, infoSize, compareHeapRecords);
461
462    /* dump the entries to the file */
463    const uint8_t* ptr = info;
464    for (size_t idx = 0; idx < recordCount; idx++) {
465        size_t size = *(size_t*) ptr;
466        size_t allocations = *(size_t*) (ptr + sizeof(size_t));
467        intptr_t* backtrace = (intptr_t*) (ptr + sizeof(size_t) * 2);
468
469        fprintf(fp, "z %d  sz %8zu  num %4zu  bt",
470                (size & SIZE_FLAG_ZYGOTE_CHILD) != 0,
471                size & ~SIZE_FLAG_ZYGOTE_CHILD,
472                allocations);
473        for (size_t bt = 0; bt < backtraceSize; bt++) {
474            if (backtrace[bt] == 0) {
475                break;
476            } else {
477                fprintf(fp, " %08x", backtrace[bt]);
478            }
479        }
480        fprintf(fp, "\n");
481
482        ptr += infoSize;
483    }
484
485    free_malloc_leak_info(info);
486
487    fprintf(fp, "MAPS\n");
488    const char* maps = "/proc/self/maps";
489    FILE* in = fopen(maps, "r");
490    if (in == NULL) {
491        fprintf(fp, "Could not open %s\n", maps);
492        return;
493    }
494    char buf[BUFSIZ];
495    while (size_t n = fread(buf, sizeof(char), BUFSIZ, in)) {
496        fwrite(buf, sizeof(char), n, fp);
497    }
498    fclose(in);
499
500    fprintf(fp, "END\n");
501}
502
503/*
504 * Dump the native heap, writing human-readable output to the specified
505 * file descriptor.
506 */
507static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject clazz,
508    jobject fileDescriptor)
509{
510    if (fileDescriptor == NULL) {
511        jniThrowNullPointerException(env, NULL);
512        return;
513    }
514    int origFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
515    if (origFd < 0) {
516        jniThrowRuntimeException(env, "Invalid file descriptor");
517        return;
518    }
519
520    /* dup() the descriptor so we don't close the original with fclose() */
521    int fd = dup(origFd);
522    if (fd < 0) {
523        ALOGW("dup(%d) failed: %s\n", origFd, strerror(errno));
524        jniThrowRuntimeException(env, "dup() failed");
525        return;
526    }
527
528    FILE* fp = fdopen(fd, "w");
529    if (fp == NULL) {
530        ALOGW("fdopen(%d) failed: %s\n", fd, strerror(errno));
531        close(fd);
532        jniThrowRuntimeException(env, "fdopen() failed");
533        return;
534    }
535
536    ALOGD("Native heap dump starting...\n");
537    dumpNativeHeap(fp);
538    ALOGD("Native heap dump complete.\n");
539
540    fclose(fp);
541}
542
543
544static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz,
545    jint pid, jstring fileName)
546{
547    if (fileName == NULL) {
548        jniThrowNullPointerException(env, NULL);
549        return;
550    }
551    const jchar* str = env->GetStringCritical(fileName, 0);
552    String8 fileName8;
553    if (str) {
554        fileName8 = String8(str, env->GetStringLength(fileName));
555        env->ReleaseStringCritical(fileName, str);
556    }
557
558    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666);  /* -rw-rw-rw- */
559    if (fd < 0) {
560        fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
561        return;
562    }
563
564    if (lseek(fd, 0, SEEK_END) < 0) {
565        fprintf(stderr, "lseek: %s\n", strerror(errno));
566    } else {
567        dump_backtrace_to_file(pid, fd);
568    }
569
570    close(fd);
571}
572
573/*
574 * JNI registration.
575 */
576
577static JNINativeMethod gMethods[] = {
578    { "getNativeHeapSize",      "()J",
579            (void*) android_os_Debug_getNativeHeapSize },
580    { "getNativeHeapAllocatedSize", "()J",
581            (void*) android_os_Debug_getNativeHeapAllocatedSize },
582    { "getNativeHeapFreeSize",  "()J",
583            (void*) android_os_Debug_getNativeHeapFreeSize },
584    { "getMemoryInfo",          "(Landroid/os/Debug$MemoryInfo;)V",
585            (void*) android_os_Debug_getDirtyPages },
586    { "getMemoryInfo",          "(ILandroid/os/Debug$MemoryInfo;)V",
587            (void*) android_os_Debug_getDirtyPagesPid },
588    { "getPss",                 "()J",
589            (void*) android_os_Debug_getPss },
590    { "getPss",                 "(I)J",
591            (void*) android_os_Debug_getPssPid },
592    { "dumpNativeHeap",         "(Ljava/io/FileDescriptor;)V",
593            (void*) android_os_Debug_dumpNativeHeap },
594    { "getBinderSentTransactions", "()I",
595            (void*) android_os_Debug_getBinderSentTransactions },
596    { "getBinderReceivedTransactions", "()I",
597            (void*) android_os_getBinderReceivedTransactions },
598    { "getBinderLocalObjectCount", "()I",
599            (void*)android_os_Debug_getLocalObjectCount },
600    { "getBinderProxyObjectCount", "()I",
601            (void*)android_os_Debug_getProxyObjectCount },
602    { "getBinderDeathObjectCount", "()I",
603            (void*)android_os_Debug_getDeathObjectCount },
604    { "dumpNativeBacktraceToFile", "(ILjava/lang/String;)V",
605            (void*)android_os_Debug_dumpNativeBacktraceToFile },
606};
607
608int register_android_os_Debug(JNIEnv *env)
609{
610    jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");
611
612    for (int i=0; i<_NUM_CORE_HEAP; i++) {
613        stat_fields[i].pss_field =
614                env->GetFieldID(clazz, stat_field_names[i].pss_name, "I");
615        stat_fields[i].privateDirty_field =
616                env->GetFieldID(clazz, stat_field_names[i].privateDirty_name, "I");
617        stat_fields[i].sharedDirty_field =
618                env->GetFieldID(clazz, stat_field_names[i].sharedDirty_name, "I");
619    }
620
621    otherStats_field = env->GetFieldID(clazz, "otherStats", "[I");
622
623    return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));
624}
625
626}; // namespace android
627