OLD | NEW |
(Empty) | |
| 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 target process is frozen (SIGSTOP) while dumping, unless the -n opt. |
| 8 // is passed (in which case the caller is responsible for un/freezing). |
| 9 // The JSON output looks like this: |
| 10 // { |
| 11 // "total_allocated": 908748493, # Total bytes allocated and not freed. |
| 12 // "num_allocs": 37542, # Number of allocations. |
| 13 // "num_stacks": 3723, # Number of allocation call-sites. |
| 14 // "allocs": # Optional. Printed only with the -x arg. |
| 15 // { |
| 16 // "beef1234": {"l": 17, "f": 1, "s": "1a"}, |
| 17 // ^ ^ ^ ^ Index of the corresponding entry in the |
| 18 // | | | next "stacks" section. Essentially a ref |
| 19 // | | | to the call site that created the alloc. |
| 20 // | | | |
| 21 // | | +-------> Flags (last arg of heap_profiler_alloc). |
| 22 // | +----------------> Length of the Alloc. |
| 23 // +-----------------------------> Start address of the Alloc (hex). |
| 24 // }, |
| 25 // "stacks": |
| 26 // { |
| 27 // "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]}, |
| 28 // ^ ^ ^ |
| 29 // | | +-----> Stack frames (absolute virtual addresses). |
| 30 // | +--------------> Bytes allocated and not freed by the call site. |
| 31 // +---------------------> Index of the entry (as for "allocs" xref). |
| 32 // Indexes are hex and might not be monotonic. |
| 33 |
| 34 #include <fcntl.h> |
| 35 #include <inttypes.h> |
| 36 #include <signal.h> |
| 37 #include <stdbool.h> |
| 38 #include <stdio.h> |
| 39 #include <stdlib.h> |
| 40 #include <string.h> |
| 41 #include <unistd.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 |
| 49 static int pid; |
| 50 |
| 51 |
| 52 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) { |
| 53 HeapStats stats; |
| 54 |
| 55 // Look for the mmap which contains the HeapStats in the target process vmem. |
| 56 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The |
| 57 // region furthermore starts with a magic marker to disambiguate. |
| 58 bool stats_mmap_found = false; |
| 59 for (;;) { |
| 60 char line[1024]; |
| 61 if (fgets(line, sizeof(line), fmaps) == NULL) |
| 62 break; |
| 63 |
| 64 uintptr_t start; |
| 65 uintptr_t end; |
| 66 char map_file[32]; |
| 67 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", |
| 68 &start, &end, map_file); |
| 69 const size_t size = end - start + 1; |
| 70 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) |
| 71 continue; |
| 72 |
| 73 // The mmap looks promising. Let's check for the magic marker. |
| 74 lseek_abs(mem_fd, start); |
| 75 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) |
| 76 continue; |
| 77 |
| 78 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { |
| 79 stats_mmap_found = true; |
| 80 break; |
| 81 } |
| 82 } |
| 83 |
| 84 if (!stats_mmap_found) { |
| 85 fprintf(stderr, "Could not find the HeapStats area. " |
| 86 "It looks like libheap_profiler is not loaded.\n"); |
| 87 return -1; |
| 88 } |
| 89 |
| 90 // Print JSON-formatted output. |
| 91 printf("{\n"); |
| 92 printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes); |
| 93 printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs); |
| 94 printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces); |
| 95 |
| 96 uint32_t dbg_counted_allocs = 0; |
| 97 size_t dbg_counted_total_alloc_bytes = 0; |
| 98 bool prepend_trailing_comma = false; // JSON syntax, I hate you. |
| 99 uint32_t i; |
| 100 |
| 101 // Dump the optional allocation table. |
| 102 if (dump_also_allocs) { |
| 103 printf(" \"allocs\": {"); |
| 104 lseek_abs(mem_fd, (uintptr_t) stats.allocs); |
| 105 for (i = 0; i < stats.max_allocs; ++i) { |
| 106 Alloc alloc; |
| 107 if (read(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) { |
| 108 fprintf(stderr, "ERROR: cannot read allocation table\n"); |
| 109 perror("read"); |
| 110 return -1; |
| 111 } |
| 112 |
| 113 // Skip empty (i.e. freed) entries. |
| 114 if (alloc.start == 0 && alloc.end == 0) |
| 115 continue; |
| 116 |
| 117 if (alloc.end < alloc.start) { |
| 118 fprintf(stderr, "ERROR: found inconsistent alloc.\n"); |
| 119 return -1; |
| 120 } |
| 121 |
| 122 size_t alloc_size = alloc.end - alloc.start + 1; |
| 123 size_t stack_idx = ( |
| 124 (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) / |
| 125 sizeof(StacktraceEntry); |
| 126 dbg_counted_total_alloc_bytes += alloc_size; |
| 127 ++dbg_counted_allocs; |
| 128 |
| 129 if (prepend_trailing_comma) |
| 130 printf(","); |
| 131 prepend_trailing_comma = true; |
| 132 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", |
| 133 alloc.start, alloc_size, alloc.flags, stack_idx); |
| 134 } |
| 135 printf("},\n"); |
| 136 |
| 137 if (dbg_counted_allocs != stats.num_allocs) { |
| 138 fprintf(stderr, |
| 139 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", |
| 140 dbg_counted_allocs, stats.num_allocs); |
| 141 return -1; |
| 142 } |
| 143 |
| 144 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| 145 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", |
| 146 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| 147 return -1; |
| 148 } |
| 149 } |
| 150 |
| 151 // Dump the distinct stack traces. |
| 152 printf(" \"stacks\": {"); |
| 153 prepend_trailing_comma = false; |
| 154 dbg_counted_total_alloc_bytes = 0; |
| 155 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); |
| 156 for (i = 0; i < stats.max_stack_traces; ++i) { |
| 157 StacktraceEntry st; |
| 158 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { |
| 159 fprintf(stderr, "ERROR: cannot read stack trace table\n"); |
| 160 perror("read"); |
| 161 return -1; |
| 162 } |
| 163 |
| 164 // Skip empty (i.e. freed) entries. |
| 165 if (st.alloc_bytes == 0) |
| 166 continue; |
| 167 |
| 168 dbg_counted_total_alloc_bytes += st.alloc_bytes; |
| 169 |
| 170 if (prepend_trailing_comma) |
| 171 printf(","); |
| 172 prepend_trailing_comma = true; |
| 173 |
| 174 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); |
| 175 size_t n = 0; |
| 176 for (;;) { |
| 177 printf("%" PRIuPTR, st.frames[n]); |
| 178 ++n; |
| 179 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) |
| 180 break; |
| 181 else |
| 182 printf(","); |
| 183 } |
| 184 printf("]}"); |
| 185 } |
| 186 printf("}\n}\n"); |
| 187 |
| 188 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| 189 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", |
| 190 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| 191 return -1; |
| 192 } |
| 193 |
| 194 fflush(stdout); |
| 195 return 0; |
| 196 } |
| 197 |
| 198 // If the dump is interrupted, resume the target process before exiting. |
| 199 static void exit_handler() { |
| 200 kill(pid, SIGCONT); |
| 201 waitpid(pid, NULL, 0); |
| 202 exit(-1); |
| 203 } |
| 204 |
| 205 static bool freeze_process() { |
| 206 if (kill(pid, SIGSTOP) != 0) { |
| 207 fprintf(stderr, "Could not freeze the target process.\n"); |
| 208 perror("kill"); |
| 209 return false; |
| 210 } |
| 211 |
| 212 signal(SIGPIPE, exit_handler); |
| 213 signal(SIGINT, exit_handler); |
| 214 return true; |
| 215 } |
| 216 |
| 217 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large |
| 218 // files like /proc/X/mem on 64-bit. |
| 219 static void lseek_abs(int fd, size_t off) { |
| 220 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) |
| 221 if (off <= OFF_T_MAX) { |
| 222 lseek(fd, (off_t) off, SEEK_SET); |
| 223 return; |
| 224 } |
| 225 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); |
| 226 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); |
| 227 } |
| 228 |
| 229 static int open_proc_mem_fd() { |
| 230 char path[64]; |
| 231 snprintf(path, sizeof(path), "/proc/%d/mem", pid); |
| 232 int mem_fd = open(path, O_RDONLY); |
| 233 if (mem_fd < 0) { |
| 234 fprintf(stderr, "Could not attach to target process virtual memory.\n"); |
| 235 perror("open"); |
| 236 } |
| 237 return mem_fd; |
| 238 } |
| 239 |
| 240 static FILE* open_proc_maps() { |
| 241 char path[64]; |
| 242 snprintf(path, sizeof(path), "/proc/%d/maps", pid); |
| 243 FILE* fmaps = fopen(path, "r"); |
| 244 if (fmaps == NULL) { |
| 245 fprintf(stderr, "Could not open %s.\n", path); |
| 246 perror("fopen"); |
| 247 } |
| 248 return fmaps; |
| 249 } |
| 250 |
| 251 int main(int argc, char** argv) { |
| 252 char c; |
| 253 int ret = 0; |
| 254 bool should_freeze_process = true; |
| 255 bool dump_also_allocs = false; |
| 256 |
| 257 |
| 258 while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) { |
| 259 switch (c) { |
| 260 case 'n': |
| 261 should_freeze_process = false; |
| 262 break; |
| 263 case 'x': |
| 264 dump_also_allocs = true; |
| 265 break; |
| 266 } |
| 267 } |
| 268 |
| 269 if (optind >= argc) { |
| 270 printf("Usage: %s [-n] [-x] pid\n", argv[0]); |
| 271 return -1; |
| 272 } |
| 273 |
| 274 pid = atoi(argv[optind]); |
| 275 |
| 276 if (should_freeze_process && !freeze_process()) |
| 277 return -1; |
| 278 |
| 279 // Wait for the process to actually freeze. |
| 280 waitpid(pid, NULL, 0); |
| 281 |
| 282 int mem_fd = open_proc_mem_fd(); |
| 283 if (mem_fd < 0) |
| 284 ret = -1; |
| 285 |
| 286 FILE* fmaps = open_proc_maps(); |
| 287 if (fmaps == NULL) |
| 288 ret = -1; |
| 289 |
| 290 if (ret == 0) |
| 291 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs); |
| 292 |
| 293 if (should_freeze_process) |
| 294 kill(pid, SIGCONT); |
| 295 |
| 296 // Cleanup. |
| 297 fflush(stdout); |
| 298 close(mem_fd); |
| 299 fclose(fmaps); |
| 300 return ret; |
| 301 } |
OLD | NEW |