|  | // 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 JSON output looks like this: | 
|  | // { | 
|  | //   "total_allocated": 908748493,   # Total bytes allocated and not freed. | 
|  | //   "num_allocs":      37542,       # Number of allocations. | 
|  | //   "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 that created the alloc. | 
|  | //      |            |        | | 
|  | //      |            |        +-------> Flags (last arg of heap_profiler_alloc). | 
|  | //      |            +----------------> Length of the Alloc. | 
|  | //      +-----------------------------> Start address of the Alloc (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 <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <inttypes.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <time.h> | 
|  | #include <unistd.h> | 
|  | #include <sys/ptrace.h> | 
|  | #include <sys/stat.h> | 
|  |  | 
|  | #include "tools/android/heap_profiler/heap_profiler.h" | 
|  |  | 
|  |  | 
|  | static void lseek_abs(int fd, size_t off); | 
|  | static void read_proc_cmdline(char* cmdline, int size); | 
|  | static ssize_t read_safe(int fd, void* buf, size_t count); | 
|  |  | 
|  | static int pid; | 
|  |  | 
|  |  | 
|  | static int dump_process_heap( | 
|  | int mem_fd, | 
|  | FILE* fmaps, | 
|  | bool dump_also_allocs, | 
|  | bool pedantic,  // Enable pedantic consistency checks on memory counters. | 
|  | char* comment) { | 
|  | HeapStats stats; | 
|  | time_t tm; | 
|  | char cmdline[512]; | 
|  |  | 
|  | tm = time(NULL); | 
|  | read_proc_cmdline(cmdline, sizeof(cmdline)); | 
|  |  | 
|  | // 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); | 
|  | ssize_t rsize = read_safe(mem_fd, &stats, sizeof(stats)); | 
|  |  | 
|  | if (rsize == -1) { | 
|  | perror("read"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (rsize < 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("  \"pid\":             %d,\n", pid); | 
|  | printf("  \"time\":            %ld,\n", tm); | 
|  | printf("  \"comment\":         \"%s\",\n", comment); | 
|  | printf("  \"cmdline\":         \"%s\",\n", cmdline); | 
|  | printf("  \"pagesize\":        %d,\n", getpagesize()); | 
|  | 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_allocs = 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) { | 
|  | Alloc alloc; | 
|  | if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) { | 
|  | fprintf(stderr, "ERROR: cannot read allocation table\n"); | 
|  | perror("read"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | // Skip empty (i.e. freed) entries. | 
|  | if (alloc.start == 0 && alloc.end == 0) | 
|  | continue; | 
|  |  | 
|  | if (alloc.end < alloc.start) { | 
|  | fprintf(stderr, "ERROR: found inconsistent alloc.\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | size_t alloc_size = alloc.end - alloc.start + 1; | 
|  | size_t stack_idx = ( | 
|  | (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) / | 
|  | sizeof(StacktraceEntry); | 
|  | dbg_counted_total_alloc_bytes += alloc_size; | 
|  | ++dbg_counted_allocs; | 
|  |  | 
|  | if (prepend_trailing_comma) | 
|  | printf(","); | 
|  | prepend_trailing_comma = true; | 
|  | printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", | 
|  | alloc.start, alloc_size, alloc.flags, stack_idx); | 
|  | } | 
|  | printf("},\n"); | 
|  |  | 
|  | if (pedantic && dbg_counted_allocs != stats.num_allocs) { | 
|  | fprintf(stderr, | 
|  | "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | 
|  | dbg_counted_allocs, stats.num_allocs); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (pedantic && 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_safe(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 (pedantic && 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; | 
|  | } | 
|  |  | 
|  | // 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 ssize_t read_safe(int fd, void* buf, size_t count) { | 
|  | ssize_t res; | 
|  | size_t bytes_read = 0; | 
|  | if (count < 0) | 
|  | return -1; | 
|  | do { | 
|  | do { | 
|  | res = read(fd, buf + bytes_read, count - bytes_read); | 
|  | } while (res == -1  && errno == EINTR); | 
|  | if (res <= 0) | 
|  | break; | 
|  | bytes_read += res; | 
|  | } while (bytes_read < count); | 
|  | return bytes_read ? bytes_read : res; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static void read_proc_cmdline(char* cmdline, int size) { | 
|  | char path[64]; | 
|  | snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); | 
|  | int cmdline_fd = open(path, O_RDONLY); | 
|  | if (cmdline_fd < 0) { | 
|  | fprintf(stderr, "Could not open %s.\n", path); | 
|  | perror("open"); | 
|  | cmdline[0] = '\0'; | 
|  | return; | 
|  | } | 
|  | int length = read_safe(cmdline_fd, cmdline, size); | 
|  | if (length < 0) { | 
|  | fprintf(stderr, "Could not read %s.\n", path); | 
|  | perror("read"); | 
|  | length = 0; | 
|  | } | 
|  | close(cmdline_fd); | 
|  | cmdline[length] = '\0'; | 
|  | } | 
|  |  | 
|  | int main(int argc, char** argv) { | 
|  | char c; | 
|  | int ret = 0; | 
|  | bool dump_also_allocs = false; | 
|  | bool pedantic = true; | 
|  | char comment[1024] = { '\0' }; | 
|  |  | 
|  | while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) { | 
|  | switch (c) { | 
|  | case 'x': | 
|  | dump_also_allocs = true; | 
|  | break; | 
|  | case 'n': | 
|  | pedantic = false; | 
|  | break; | 
|  | case 'c': | 
|  | strlcpy(comment, optarg, sizeof(comment)); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (optind >= argc) { | 
|  | printf("Usage: %s [-n] [-x] [-c comment] pid\n" | 
|  | "  -n: Skip pedantic checks on dump consistency.\n" | 
|  | "  -x: Extended dump, includes individual allocations.\n" | 
|  | "  -c: Appends the given comment to the JSON dump.\n", | 
|  | argv[0]); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | pid = atoi(argv[optind]); | 
|  |  | 
|  | if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { | 
|  | perror("ptrace"); | 
|  | 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, pedantic, comment); | 
|  |  | 
|  | ptrace(PTRACE_DETACH, pid, NULL, NULL); | 
|  |  | 
|  | // Cleanup. | 
|  | fflush(stdout); | 
|  | close(mem_fd); | 
|  | fclose(fmaps); | 
|  | return ret; | 
|  | } |