| Index: tools/android/heap_profiler/heap_dump.c | 
| diff --git a/tools/android/heap_profiler/heap_dump.c b/tools/android/heap_profiler/heap_dump.c | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..aa4b113c5d53f5d849be4a85b8a65b92002cc9c2 | 
| --- /dev/null | 
| +++ b/tools/android/heap_profiler/heap_dump.c | 
| @@ -0,0 +1,300 @@ | 
| +// Copyright 2014 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +// The client dump tool for libheap_profiler. It attaches to a process (given | 
| +// its pid) and dumps all the libheap_profiler tracking information in JSON. | 
| +// The target process is frozen (SIGSTOP) while dumping, unless the -n opt. | 
| +// is passed (in which case the caller is responsible for un/freezing). | 
| +// The JSON output looks like this: | 
| +// { | 
| +//   "total_allocated": 908748493,   # Total bytes allocated and not freed. | 
| +//   "num_allocs":      37542,       # Number of virtual memory areas (VMAs) | 
| +//   "num_stacks":      3723,        # Number of allocation call-sites. | 
| +//   "allocs":                       # Optional. Printed only with the -x arg. | 
| +//   { | 
| +//     "beef1234": {"l": 17, "f": 1, "s": "1a"}, | 
| +//      ^            ^        ^       ^ Index of the corresponding entry in the | 
| +//      |            |        |         next "stacks" section. Essentially a ref | 
| +//      |            |        |         to the call site which created the vma. | 
| +//      |            |        | | 
| +//      |            |        +-------> Last arg of heap_profiler_alloc(). | 
| +//      |            +----------------> Length of the VMA. | 
| +//      +-----------------------------> Start address of the VMA (hex). | 
| +//   }, | 
| +//   "stacks": | 
| +//   { | 
| +//      "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]}, | 
| +//       ^      ^        ^ | 
| +//       |      |        +-----> Stack frames (absolute virtual addresses). | 
| +//       |      +--------------> Bytes allocated and not freed by the call site. | 
| +//       +---------------------> Index of the entry (as for "allocs" xref). | 
| +//                               Indexes are hex and might not be monotonic. | 
| + | 
| +#include <fcntl.h> | 
| +#include <inttypes.h> | 
| +#include <signal.h> | 
| +#include <stdbool.h> | 
| +#include <stdio.h> | 
| +#include <stdlib.h> | 
| +#include <string.h> | 
| +#include <unistd.h> | 
| +#include <sys/stat.h> | 
| + | 
| +#include "tools/android/heap_profiler/heap_profiler.h" | 
| + | 
| + | 
| +static void lseek_abs(int fd, size_t off); | 
| + | 
| +static int pid; | 
| + | 
| + | 
| +static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) { | 
| +  HeapStats stats; | 
| + | 
| +  // Look for the mmap which contains the HeapStats in the target process vmem. | 
| +  // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The | 
| +  // region furthermore starts with a magic marker to disambiguate. | 
| +  bool stats_mmap_found = false; | 
| +  for (;;) { | 
| +    char line[1024]; | 
| +    if (fgets(line, sizeof(line), fmaps) == NULL) | 
| +      break; | 
| + | 
| +    uintptr_t start; | 
| +    uintptr_t end; | 
| +    char map_file[32]; | 
| +    int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", | 
| +                     &start, &end, map_file); | 
| +    const size_t size = end - start + 1; | 
| +    if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) | 
| +      continue; | 
| + | 
| +    // The mmap looks promising. Let's check for the magic marker. | 
| +    lseek_abs(mem_fd, start); | 
| +    if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) | 
| +      continue; | 
| + | 
| +    if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { | 
| +      stats_mmap_found = true; | 
| +      break; | 
| +    } | 
| +  } | 
| + | 
| +  if (!stats_mmap_found) { | 
| +    fprintf(stderr, "Could not find the HeapStats area. " | 
| +                    "It looks like libheap_profiler is not loaded.\n"); | 
| +    return -1; | 
| +  } | 
| + | 
| +  // Print JSON-formatted output. | 
| +  printf("{\n"); | 
| +  printf("  \"total_allocated\": %zu,\n", stats.total_alloc_bytes); | 
| +  printf("  \"num_allocs\":      %"PRIu32",\n", stats.num_allocs); | 
| +  printf("  \"num_stacks\":      %"PRIu32",\n", stats.num_stack_traces); | 
| + | 
| +  uint32_t dbg_counted_vmas = 0; | 
| +  size_t dbg_counted_total_alloc_bytes = 0; | 
| +  bool prepend_trailing_comma = false;  // JSON syntax, I hate you. | 
| +  uint32_t i; | 
| + | 
| +  // Dump the optional allocation table. | 
| +  if (dump_also_allocs) { | 
| +    printf("  \"allocs\": {"); | 
| +    lseek_abs(mem_fd, (uintptr_t) stats.allocs); | 
| +    for (i = 0; i < stats.max_allocs; ++i) { | 
| +      VMA vma; | 
| +      if (read(mem_fd, &vma, sizeof(vma)) != sizeof(vma)) { | 
| +        fprintf(stderr, "ERROR: cannot read allocation table\n"); | 
| +        perror("read"); | 
| +        return -1; | 
| +      } | 
| + | 
| +      // Skip empty (i.e. freed) entries. | 
| +      if (vma.start == 0 && vma.end == 0) | 
| +        continue; | 
| + | 
| +      if (vma.end < vma.start) { | 
| +        fprintf(stderr, "ERROR: found inconsistent vma.\n"); | 
| +        return -1; | 
| +      } | 
| + | 
| +      size_t vma_size = vma.end - vma.start + 1; | 
| +      size_t stack_idx = ((uintptr_t) vma.st - (uintptr_t) stats.stack_traces) / | 
| +                         sizeof(StacktraceEntry); | 
| +      dbg_counted_total_alloc_bytes += vma_size; | 
| +      ++dbg_counted_vmas; | 
| + | 
| +      if (prepend_trailing_comma) | 
| +        printf(","); | 
| +      prepend_trailing_comma = true; | 
| +      printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", | 
| +             vma.start, vma_size, vma.flags, stack_idx); | 
| +    } | 
| +    printf("},\n"); | 
| + | 
| +    if (dbg_counted_vmas != stats.num_allocs) { | 
| +      fprintf(stderr, | 
| +              "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | 
| +              dbg_counted_vmas, stats.num_allocs); | 
| +      return -1; | 
| +    } | 
| + | 
| +    if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | 
| +      fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", | 
| +              dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | 
| +      return -1; | 
| +    } | 
| +  } | 
| + | 
| +  // Dump the distinct stack traces. | 
| +  printf("  \"stacks\": {"); | 
| +  prepend_trailing_comma = false; | 
| +  dbg_counted_total_alloc_bytes = 0; | 
| +  lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); | 
| +  for (i = 0; i < stats.max_stack_traces; ++i) { | 
| +    StacktraceEntry st; | 
| +    if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { | 
| +      fprintf(stderr, "ERROR: cannot read stack trace table\n"); | 
| +      perror("read"); | 
| +      return -1; | 
| +    } | 
| + | 
| +    // Skip empty (i.e. freed) entries. | 
| +    if (st.alloc_bytes == 0) | 
| +      continue; | 
| + | 
| +    dbg_counted_total_alloc_bytes += st.alloc_bytes; | 
| + | 
| +    if (prepend_trailing_comma) | 
| +      printf(","); | 
| +    prepend_trailing_comma = true; | 
| + | 
| +    printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); | 
| +    size_t n = 0; | 
| +    for (;;) { | 
| +      printf("%" PRIuPTR, st.frames[n]); | 
| +      ++n; | 
| +      if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) | 
| +        break; | 
| +      else | 
| +        printf(","); | 
| +    } | 
| +    printf("]}"); | 
| +  } | 
| +  printf("}\n}\n"); | 
| + | 
| +  if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | 
| +    fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", | 
| +            dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | 
| +    return -1; | 
| +  } | 
| + | 
| +  fflush(stdout); | 
| +  return 0; | 
| +} | 
| + | 
| +// If the dump is interrupted, resume the target process before exiting. | 
| +static void exit_handler() { | 
| +  kill(pid, SIGCONT); | 
| +  waitpid(pid, NULL, 0); | 
| +  exit(-1); | 
| +} | 
| + | 
| +static bool freeze_process() { | 
| +  if (kill(pid, SIGSTOP) != 0) { | 
| +    fprintf(stderr, "Could not freeze the target process.\n"); | 
| +    perror("kill"); | 
| +    return false; | 
| +  } | 
| + | 
| +  signal(SIGPIPE, exit_handler); | 
| +  signal(SIGINT, exit_handler); | 
| +  return true; | 
| +} | 
| + | 
| +// Unfortunately lseek takes a *signed* offset, which is unsuitable for large | 
| +// files like /proc/X/mem on 64-bit. | 
| +static void lseek_abs(int fd, size_t off) { | 
| +#define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) | 
| +  if (off <= OFF_T_MAX) { | 
| +    lseek(fd, (off_t) off, SEEK_SET); | 
| +    return; | 
| +  } | 
| +  lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); | 
| +  lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); | 
| +} | 
| + | 
| +static int open_proc_mem_fd() { | 
| +  char path[64]; | 
| +  snprintf(path, sizeof(path), "/proc/%d/mem", pid); | 
| +  int mem_fd = open(path, O_RDONLY); | 
| +  if (mem_fd < 0) { | 
| +    fprintf(stderr, "Could not attach to target process virtual memory.\n"); | 
| +    perror("open"); | 
| +  } | 
| +  return mem_fd; | 
| +} | 
| + | 
| +static FILE* open_proc_maps() { | 
| +  char path[64]; | 
| +  snprintf(path, sizeof(path), "/proc/%d/maps", pid); | 
| +  FILE* fmaps = fopen(path, "r"); | 
| +  if (fmaps == NULL) { | 
| +    fprintf(stderr, "Could not open %s.\n", path); | 
| +    perror("fopen"); | 
| +  } | 
| +  return fmaps; | 
| +} | 
| + | 
| +int main(int argc, char** argv) { | 
| +  char c; | 
| +  int ret = 0; | 
| +  bool should_freeze_process = true; | 
| +  bool dump_also_allocs = false; | 
| + | 
| + | 
| +  while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) { | 
| +   switch (c) { | 
| +      case 'n': | 
| +        should_freeze_process = false; | 
| +        break; | 
| +      case 'x': | 
| +        dump_also_allocs = true; | 
| +        break; | 
| +     } | 
| +  } | 
| + | 
| +  if (optind >= argc) { | 
| +    printf("Usage: %s [-n] [-x] pid\n", argv[0]); | 
| +    return -1; | 
| +  } | 
| + | 
| +  pid = atoi(argv[optind]); | 
| + | 
| +  if (should_freeze_process && !freeze_process()) | 
| +    return -1; | 
| + | 
| +  // Wait for the process to actually freeze. | 
| +  waitpid(pid, NULL, 0); | 
| + | 
| +  int mem_fd = open_proc_mem_fd(); | 
| +  if (mem_fd < 0) | 
| +    ret = -1; | 
| + | 
| +  FILE* fmaps = open_proc_maps(); | 
| +  if (fmaps == NULL) | 
| +    ret = -1; | 
| + | 
| +  if (ret == 0) | 
| +    ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs); | 
| + | 
| +  if (should_freeze_process) | 
| +    kill(pid, SIGCONT); | 
| + | 
| +  // Cleanup. | 
| +  fflush(stdout); | 
| +  close(mem_fd); | 
| +  fclose(fmaps); | 
| +  return ret; | 
| +} | 
|  |