Home | History | Annotate | Download | only in heap_profiler
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // The client dump tool for libheap_profiler. It attaches to a process (given
      6 // its pid) and dumps all the libheap_profiler tracking information in JSON.
      7 // The JSON output looks like this:
      8 // {
      9 //   "total_allocated": 908748493,   # Total bytes allocated and not freed.
     10 //   "num_allocs":      37542,       # Number of allocations.
     11 //   "num_stacks":      3723,        # Number of allocation call-sites.
     12 //   "allocs":                       # Optional. Printed only with the -x arg.
     13 //   {
     14 //     "beef1234": {"l": 17, "f": 1, "s": "1a"},
     15 //      ^            ^        ^       ^ Index of the corresponding entry in the
     16 //      |            |        |         next "stacks" section. Essentially a ref
     17 //      |            |        |         to the call site that created the alloc.
     18 //      |            |        |
     19 //      |            |        +-------> Flags (last arg of heap_profiler_alloc).
     20 //      |            +----------------> Length of the Alloc.
     21 //      +-----------------------------> Start address of the Alloc (hex).
     22 //   },
     23 //   "stacks":
     24 //   {
     25 //      "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]},
     26 //       ^      ^        ^
     27 //       |      |        +-----> Stack frames (absolute virtual addresses).
     28 //       |      +--------------> Bytes allocated and not freed by the call site.
     29 //       +---------------------> Index of the entry (as for "allocs" xref).
     30 //                               Indexes are hex and might not be monotonic.
     31 
     32 #include <errno.h>
     33 #include <fcntl.h>
     34 #include <inttypes.h>
     35 #include <stdbool.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <string.h>
     39 #include <time.h>
     40 #include <unistd.h>
     41 #include <sys/ptrace.h>
     42 #include <sys/stat.h>
     43 
     44 #include "tools/android/heap_profiler/heap_profiler.h"
     45 
     46 
     47 static void lseek_abs(int fd, size_t off);
     48 static void read_proc_cmdline(char* cmdline, int size);
     49 static ssize_t read_safe(int fd, void* buf, size_t count);
     50 
     51 static int pid;
     52 
     53 
     54 static int dump_process_heap(
     55     int mem_fd,
     56     FILE* fmaps,
     57     bool dump_also_allocs,
     58     bool pedantic,  // Enable pedantic consistency checks on memory counters.
     59     char* comment) {
     60   HeapStats stats;
     61   time_t tm;
     62   char cmdline[512];
     63 
     64   tm = time(NULL);
     65   read_proc_cmdline(cmdline, sizeof(cmdline));
     66 
     67   // Look for the mmap which contains the HeapStats in the target process vmem.
     68   // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
     69   // region furthermore starts with a magic marker to disambiguate.
     70   bool stats_mmap_found = false;
     71   for (;;) {
     72     char line[1024];
     73     if (fgets(line, sizeof(line), fmaps) == NULL)
     74       break;
     75 
     76     uintptr_t start;
     77     uintptr_t end;
     78     char map_file[32];
     79     int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
     80                      &start, &end, map_file);
     81     const size_t size = end - start + 1;
     82     if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
     83       continue;
     84 
     85     // The mmap looks promising. Let's check for the magic marker.
     86     lseek_abs(mem_fd, start);
     87     ssize_t rsize = read_safe(mem_fd, &stats, sizeof(stats));
     88 
     89     if (rsize == -1) {
     90       perror("read");
     91       return -1;
     92     }
     93 
     94     if (rsize < sizeof(stats))
     95       continue;
     96 
     97     if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
     98       stats_mmap_found = true;
     99       break;
    100     }
    101   }
    102 
    103   if (!stats_mmap_found) {
    104     fprintf(stderr, "Could not find the HeapStats area. "
    105                     "It looks like libheap_profiler is not loaded.\n");
    106     return -1;
    107   }
    108 
    109   // Print JSON-formatted output.
    110   printf("{\n");
    111   printf("  \"pid\":             %d,\n", pid);
    112   printf("  \"time\":            %ld,\n", tm);
    113   printf("  \"comment\":         \"%s\",\n", comment);
    114   printf("  \"cmdline\":         \"%s\",\n", cmdline);
    115   printf("  \"pagesize\":        %d,\n", getpagesize());
    116   printf("  \"total_allocated\": %zu,\n", stats.total_alloc_bytes);
    117   printf("  \"num_allocs\":      %"PRIu32",\n", stats.num_allocs);
    118   printf("  \"num_stacks\":      %"PRIu32",\n", stats.num_stack_traces);
    119 
    120   uint32_t dbg_counted_allocs = 0;
    121   size_t dbg_counted_total_alloc_bytes = 0;
    122   bool prepend_trailing_comma = false;  // JSON syntax, I hate you.
    123   uint32_t i;
    124 
    125   // Dump the optional allocation table.
    126   if (dump_also_allocs) {
    127     printf("  \"allocs\": {");
    128     lseek_abs(mem_fd, (uintptr_t) stats.allocs);
    129     for (i = 0; i < stats.max_allocs; ++i) {
    130       Alloc alloc;
    131       if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) {
    132         fprintf(stderr, "ERROR: cannot read allocation table\n");
    133         perror("read");
    134         return -1;
    135       }
    136 
    137       // Skip empty (i.e. freed) entries.
    138       if (alloc.start == 0 && alloc.end == 0)
    139         continue;
    140 
    141       if (alloc.end < alloc.start) {
    142         fprintf(stderr, "ERROR: found inconsistent alloc.\n");
    143         return -1;
    144       }
    145 
    146       size_t alloc_size = alloc.end - alloc.start + 1;
    147       size_t stack_idx = (
    148           (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) /
    149           sizeof(StacktraceEntry);
    150       dbg_counted_total_alloc_bytes += alloc_size;
    151       ++dbg_counted_allocs;
    152 
    153       if (prepend_trailing_comma)
    154         printf(",");
    155       prepend_trailing_comma = true;
    156       printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}",
    157              alloc.start, alloc_size, alloc.flags, stack_idx);
    158     }
    159     printf("},\n");
    160 
    161     if (pedantic && dbg_counted_allocs != stats.num_allocs) {
    162       fprintf(stderr,
    163               "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
    164               dbg_counted_allocs, stats.num_allocs);
    165       return -1;
    166     }
    167 
    168     if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
    169       fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
    170               dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
    171       return -1;
    172     }
    173   }
    174 
    175   // Dump the distinct stack traces.
    176   printf("  \"stacks\": {");
    177   prepend_trailing_comma = false;
    178   dbg_counted_total_alloc_bytes = 0;
    179   lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
    180   for (i = 0; i < stats.max_stack_traces; ++i) {
    181     StacktraceEntry st;
    182     if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) {
    183       fprintf(stderr, "ERROR: cannot read stack trace table\n");
    184       perror("read");
    185       return -1;
    186     }
    187 
    188     // Skip empty (i.e. freed) entries.
    189     if (st.alloc_bytes == 0)
    190       continue;
    191 
    192     dbg_counted_total_alloc_bytes += st.alloc_bytes;
    193 
    194     if (prepend_trailing_comma)
    195       printf(",");
    196     prepend_trailing_comma = true;
    197 
    198     printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes);
    199     size_t n = 0;
    200     for (;;) {
    201       printf("%" PRIuPTR, st.frames[n]);
    202       ++n;
    203       if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
    204         break;
    205       else
    206         printf(",");
    207     }
    208     printf("]}");
    209   }
    210   printf("}\n}\n");
    211 
    212   if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
    213     fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
    214             dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
    215     return -1;
    216   }
    217 
    218   fflush(stdout);
    219   return 0;
    220 }
    221 
    222 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large
    223 // files like /proc/X/mem on 64-bit.
    224 static void lseek_abs(int fd, size_t off) {
    225 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
    226   if (off <= OFF_T_MAX) {
    227     lseek(fd, (off_t) off, SEEK_SET);
    228     return;
    229   }
    230   lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
    231   lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
    232 }
    233 
    234 static ssize_t read_safe(int fd, void* buf, size_t count) {
    235   ssize_t res;
    236   size_t bytes_read = 0;
    237   if (count < 0)
    238     return -1;
    239   do {
    240     do {
    241       res = read(fd, buf + bytes_read, count - bytes_read);
    242     } while (res == -1  && errno == EINTR);
    243     if (res <= 0)
    244       break;
    245     bytes_read += res;
    246   } while (bytes_read < count);
    247   return bytes_read ? bytes_read : res;
    248 }
    249 
    250 static int open_proc_mem_fd() {
    251   char path[64];
    252   snprintf(path, sizeof(path), "/proc/%d/mem", pid);
    253   int mem_fd = open(path, O_RDONLY);
    254   if (mem_fd < 0) {
    255     fprintf(stderr, "Could not attach to target process virtual memory.\n");
    256     perror("open");
    257   }
    258   return mem_fd;
    259 }
    260 
    261 static FILE* open_proc_maps() {
    262   char path[64];
    263   snprintf(path, sizeof(path), "/proc/%d/maps", pid);
    264   FILE* fmaps = fopen(path, "r");
    265   if (fmaps == NULL) {
    266     fprintf(stderr, "Could not open %s.\n", path);
    267     perror("fopen");
    268   }
    269   return fmaps;
    270 }
    271 
    272 static void read_proc_cmdline(char* cmdline, int size) {
    273   char path[64];
    274   snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
    275   int cmdline_fd = open(path, O_RDONLY);
    276   if (cmdline_fd < 0) {
    277     fprintf(stderr, "Could not open %s.\n", path);
    278     perror("open");
    279     cmdline[0] = '\0';
    280     return;
    281   }
    282   int length = read_safe(cmdline_fd, cmdline, size);
    283   if (length < 0) {
    284     fprintf(stderr, "Could not read %s.\n", path);
    285     perror("read");
    286     length = 0;
    287   }
    288   close(cmdline_fd);
    289   cmdline[length] = '\0';
    290 }
    291 
    292 int main(int argc, char** argv) {
    293   char c;
    294   int ret = 0;
    295   bool dump_also_allocs = false;
    296   bool pedantic = true;
    297   char comment[1024] = { '\0' };
    298 
    299   while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) {
    300    switch (c) {
    301       case 'x':
    302         dump_also_allocs = true;
    303         break;
    304       case 'n':
    305         pedantic = false;
    306         break;
    307       case 'c':
    308         strlcpy(comment, optarg, sizeof(comment));
    309         break;
    310      }
    311   }
    312 
    313   if (optind >= argc) {
    314     printf("Usage: %s [-n] [-x] [-c comment] pid\n"
    315            "  -n: Skip pedantic checks on dump consistency.\n"
    316            "  -x: Extended dump, includes individual allocations.\n"
    317            "  -c: Appends the given comment to the JSON dump.\n",
    318            argv[0]);
    319     return -1;
    320   }
    321 
    322   pid = atoi(argv[optind]);
    323 
    324   if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
    325     perror("ptrace");
    326     return -1;
    327   }
    328 
    329   // Wait for the process to actually freeze.
    330   waitpid(pid, NULL, 0);
    331 
    332   int mem_fd = open_proc_mem_fd();
    333   if (mem_fd < 0)
    334     ret = -1;
    335 
    336   FILE* fmaps = open_proc_maps();
    337   if (fmaps == NULL)
    338     ret = -1;
    339 
    340   if (ret == 0)
    341     ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment);
    342 
    343   ptrace(PTRACE_DETACH, pid, NULL, NULL);
    344 
    345   // Cleanup.
    346   fflush(stdout);
    347   close(mem_fd);
    348   fclose(fmaps);
    349   return ret;
    350 }
    351