Chromium Code Reviews| 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 virtual memory areas (VMAs) | |
| 13 // "num_stacks": 3723, # Number of allocation call-sites. | |
| 14 // "allocs": # Optional. Printed only with the -x arg. | |
| 15 // { | |
| 16 // 1121435680: {"len": 17, "flags": 1, "stack": 0}, | |
| 17 // ^ ^ ^ ^ Index of the corresponding entry | |
| 18 // | | | in the next "stacks" section. The | |
| 19 // | | | call site which created the vma. | |
| 20 // | | | | |
| 21 // | | +---------> Last arg of heap_profiler_alloc(). | |
| 22 // | +---------------------> Length of the VMA. | |
| 23 // +-----------------------------------> Start address of the VMA. | |
| 24 // }, | |
| 25 // "stacks": | |
| 26 // { | |
| 27 // 3: {"total": 17, "frames": [1074792772, 1100849864, 1100850688, ...]}, | |
|
bulach
2014/06/09 17:23:28
I suppose this is running on the device, right?
tr
Primiano Tucci (use gerrit)
2014/06/09 19:10:52
Oh, nice point. It's a lot of data actually (perha
| |
| 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 might not be continuous or 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 #include "heap_profiler.h" | |
| 44 | |
| 45 | |
| 46 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs); | |
| 47 static void lseek_abs(int fd, size_t off); | |
| 48 | |
| 49 static int pid; | |
| 50 | |
| 51 // If the dump is interrupted, resume the target process before exiting. | |
| 52 static void exit_handler() { | |
| 53 kill(pid, SIGCONT); | |
| 54 waitpid(pid, NULL, 0); | |
| 55 exit(-1); | |
| 56 } | |
| 57 | |
| 58 int main(int argc, char** argv) { | |
|
bulach
2014/06/09 17:23:28
I'd suggest moving the main to the bottom, and the
Primiano Tucci (use gerrit)
2014/06/09 19:10:52
Done.
To be honest in the long term I consdider me
| |
| 59 char c; | |
| 60 bool should_stop_process = true; | |
| 61 bool dump_also_allocs = false; | |
| 62 | |
| 63 while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) { | |
| 64 switch (c) { | |
| 65 case 'n': | |
| 66 should_stop_process = false; | |
| 67 break; | |
| 68 case 'x': | |
| 69 dump_also_allocs = true; | |
| 70 break; | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 if (optind >= argc) { | |
| 75 printf("Usage: %s [-n] [-x] pid\n", argv[0]); | |
| 76 return -1; | |
| 77 } | |
| 78 | |
| 79 pid = atoi(argv[optind]); | |
| 80 | |
| 81 if (should_stop_process) { | |
| 82 if (kill(pid, SIGSTOP) != 0) { | |
| 83 fprintf(stderr, "Could not freeze the target process.\n"); | |
| 84 perror("kill"); | |
| 85 return -1; | |
| 86 } | |
| 87 signal(SIGPIPE, exit_handler); | |
| 88 signal(SIGINT, exit_handler); | |
|
bulach
2014/06/09 17:23:28
could split this into a few smaller functions, say
| |
| 89 } | |
| 90 | |
| 91 // Wait for the process to actually freeze. | |
| 92 waitpid(pid, NULL, 0); | |
| 93 | |
| 94 int ret = 0; | |
| 95 char path[64]; | |
| 96 snprintf(path, sizeof(path), "/proc/%d/mem", pid); | |
| 97 int mem_fd = open(path, O_RDONLY); | |
| 98 if (mem_fd < 0) { | |
| 99 fprintf(stderr, "Could not attach to target process virtual memory.\n"); | |
| 100 perror("open"); | |
| 101 ret = -1; | |
| 102 } | |
| 103 | |
| 104 snprintf(path, sizeof(path), "/proc/%d/maps", pid); | |
| 105 FILE* fmaps = fopen(path, "r"); | |
| 106 if (fmaps == NULL) { | |
| 107 fprintf(stderr, "Could not open %s.\n", path); | |
| 108 perror("fopen"); | |
| 109 ret = -1; | |
| 110 } | |
|
bulach
2014/06/09 17:23:28
similarly, something like |get_mem_fd()|, and |get
Primiano Tucci (use gerrit)
2014/06/09 19:10:52
Done.
| |
| 111 | |
| 112 if (ret == 0) | |
| 113 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs); | |
| 114 | |
| 115 if (should_stop_process) | |
| 116 kill(pid, SIGCONT); | |
| 117 | |
| 118 // Cleanup. | |
| 119 fflush(stdout); | |
| 120 close(mem_fd); | |
| 121 fclose(fmaps); | |
| 122 return ret; | |
| 123 } | |
| 124 | |
| 125 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) { | |
| 126 HeapStats stats; | |
| 127 | |
| 128 // Look for the mmap which contains the HeapStats in the target process vmem. | |
| 129 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The | |
| 130 // region furthermore starts with a magic marker to disambiguate. | |
| 131 bool stats_mmap_found = false; | |
| 132 for (;;) { | |
| 133 char line[1024]; | |
| 134 if (fgets(line, sizeof(line), fmaps) == NULL) | |
| 135 break; | |
| 136 | |
| 137 uintptr_t start; | |
| 138 uintptr_t end; | |
| 139 char map_file[32]; | |
| 140 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", | |
| 141 &start, &end, map_file); | |
| 142 const size_t size = end - start + 1; | |
| 143 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) | |
| 144 continue; | |
| 145 | |
| 146 // The mmap looks promising. Let's check for the magic marker. | |
| 147 lseek_abs(mem_fd, start); | |
| 148 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) | |
| 149 continue; | |
| 150 | |
| 151 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { | |
| 152 stats_mmap_found = true; | |
| 153 break; | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 if (!stats_mmap_found) { | |
| 158 fprintf(stderr, "Could not find the HeapStats area. " | |
| 159 "It looks like libheap_profiler is not loaded.\n"); | |
| 160 return -1; | |
| 161 } | |
| 162 | |
| 163 // Print JSON-formatted output. | |
| 164 printf("{\n"); | |
| 165 printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes); | |
| 166 printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs); | |
| 167 printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces); | |
| 168 | |
| 169 uint32_t dbg_counted_vmas = 0; | |
| 170 size_t dbg_counted_total_alloc_bytes = 0; | |
| 171 bool prepend_trailing_comma = false; // JSON syntax, I hate you. | |
| 172 uint32_t i; | |
| 173 | |
| 174 // Dump the optional allocation table. | |
| 175 if (dump_also_allocs) { | |
| 176 printf(" \"allocs\":\n"); | |
| 177 printf(" {\n"); | |
| 178 lseek_abs(mem_fd, (uintptr_t) stats.allocs); | |
| 179 for (i = 0; i < stats.max_allocs; ++i) { | |
| 180 VMA vma; | |
| 181 if (read(mem_fd, &vma, sizeof(vma)) != sizeof(vma)) { | |
| 182 fprintf(stderr, "ERROR: cannot read allocation table\n"); | |
| 183 perror("read"); | |
| 184 return -1; | |
| 185 } | |
| 186 | |
| 187 // Skip empty (i.e. freed) entries. | |
| 188 if (vma.start == 0 && vma.end == 0) | |
| 189 continue; | |
| 190 | |
| 191 if (vma.end < vma.start) { | |
| 192 fprintf(stderr, "ERROR: found inconsistent vma.\n"); | |
| 193 return -1; | |
| 194 } | |
| 195 | |
| 196 size_t vma_size = vma.end - vma.start + 1; | |
| 197 size_t stack_idx = ((uintptr_t) vma.st - (uintptr_t) stats.stack_traces) / | |
| 198 sizeof(StacktraceEntry); | |
| 199 dbg_counted_total_alloc_bytes += vma_size; | |
| 200 ++dbg_counted_vmas; | |
| 201 | |
| 202 if (prepend_trailing_comma) | |
| 203 printf(",\n"); | |
| 204 prepend_trailing_comma = true; | |
| 205 printf(" %"PRIuPTR": " | |
| 206 "{\"len\": %zu, \"flags\": %"PRIu32", \"stack\": %zu}", | |
| 207 vma.start, vma_size, vma.flags, stack_idx); | |
| 208 } | |
| 209 printf("\n },\n"); | |
| 210 | |
| 211 if (dbg_counted_vmas != stats.num_allocs) { | |
| 212 fprintf(stderr, | |
| 213 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | |
| 214 dbg_counted_vmas, stats.num_allocs); | |
| 215 return -1; | |
| 216 } | |
| 217 | |
| 218 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
| 219 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", | |
| 220 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
| 221 return -1; | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 // Dump the distinct stack traces. | |
| 226 printf(" \"stacks\":\n"); | |
| 227 printf(" {\n"); | |
| 228 prepend_trailing_comma = false; | |
| 229 dbg_counted_total_alloc_bytes = 0; | |
| 230 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); | |
| 231 for (i = 0; i < stats.max_stack_traces; ++i) { | |
| 232 StacktraceEntry st; | |
| 233 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { | |
| 234 fprintf(stderr, "ERROR: cannot read stack trace table\n"); | |
| 235 perror("read"); | |
| 236 return -1; | |
| 237 } | |
| 238 | |
| 239 // Skip empty (i.e. freed) entries. | |
| 240 if (st.alloc_bytes == 0) | |
| 241 continue; | |
| 242 | |
| 243 dbg_counted_total_alloc_bytes += st.alloc_bytes; | |
| 244 | |
| 245 if (prepend_trailing_comma) | |
| 246 printf(",\n"); | |
| 247 prepend_trailing_comma = true; | |
| 248 | |
| 249 printf(" %"PRIu32": {\"total\": %zu, \"frames\": [", i, st.alloc_bytes); | |
| 250 size_t n = | |
| 251 0; | |
| 252 for (;;) { | |
| 253 printf("%" PRIuPTR, st.frames[n]); | |
| 254 ++n; | |
| 255 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) | |
| 256 break; | |
| 257 else | |
| 258 printf(", "); | |
| 259 } | |
| 260 printf("]}"); | |
| 261 } | |
| 262 printf("\n }\n"); | |
| 263 printf("}\n"); | |
| 264 | |
| 265 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
| 266 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", | |
| 267 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
| 268 return -1; | |
| 269 } | |
| 270 | |
| 271 fflush(stdout); | |
| 272 return 0; | |
| 273 } | |
| 274 | |
| 275 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large | |
| 276 // files like /proc/X/mem on 64-bit. | |
| 277 static void lseek_abs(int fd, size_t off) { | |
| 278 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) | |
| 279 if (off <= OFF_T_MAX) { | |
| 280 lseek(fd, (off_t) off, SEEK_SET); | |
| 281 return; | |
| 282 } | |
| 283 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); | |
| 284 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); | |
| 285 } | |
| OLD | NEW |