Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // The client dump tool for libheap_profiler. It attaches to a process (given | 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. | 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: | 7 // The JSON output looks like this: |
| 10 // { | 8 // { |
| 11 // "total_allocated": 908748493, # Total bytes allocated and not freed. | 9 // "total_allocated": 908748493, # Total bytes allocated and not freed. |
| 12 // "num_allocs": 37542, # Number of allocations. | 10 // "num_allocs": 37542, # Number of allocations. |
| 13 // "num_stacks": 3723, # Number of allocation call-sites. | 11 // "num_stacks": 3723, # Number of allocation call-sites. |
| 14 // "allocs": # Optional. Printed only with the -x arg. | 12 // "allocs": # Optional. Printed only with the -x arg. |
| 15 // { | 13 // { |
| 16 // "beef1234": {"l": 17, "f": 1, "s": "1a"}, | 14 // "beef1234": {"l": 17, "f": 1, "s": "1a"}, |
| 17 // ^ ^ ^ ^ Index of the corresponding entry in the | 15 // ^ ^ ^ ^ Index of the corresponding entry in the |
| 18 // | | | next "stacks" section. Essentially a ref | 16 // | | | next "stacks" section. Essentially a ref |
| 19 // | | | to the call site that created the alloc. | 17 // | | | to the call site that created the alloc. |
| 20 // | | | | 18 // | | | |
| 21 // | | +-------> Flags (last arg of heap_profiler_alloc). | 19 // | | +-------> Flags (last arg of heap_profiler_alloc). |
| 22 // | +----------------> Length of the Alloc. | 20 // | +----------------> Length of the Alloc. |
| 23 // +-----------------------------> Start address of the Alloc (hex). | 21 // +-----------------------------> Start address of the Alloc (hex). |
| 24 // }, | 22 // }, |
| 25 // "stacks": | 23 // "stacks": |
| 26 // { | 24 // { |
| 27 // "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]}, | 25 // "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]}, |
| 28 // ^ ^ ^ | 26 // ^ ^ ^ |
| 29 // | | +-----> Stack frames (absolute virtual addresses). | 27 // | | +-----> Stack frames (absolute virtual addresses). |
| 30 // | +--------------> Bytes allocated and not freed by the call site. | 28 // | +--------------> Bytes allocated and not freed by the call site. |
| 31 // +---------------------> Index of the entry (as for "allocs" xref). | 29 // +---------------------> Index of the entry (as for "allocs" xref). |
| 32 // Indexes are hex and might not be monotonic. | 30 // Indexes are hex and might not be monotonic. |
| 33 | 31 |
| 34 #include <fcntl.h> | 32 #include <fcntl.h> |
| 35 #include <inttypes.h> | 33 #include <inttypes.h> |
| 36 #include <signal.h> | |
| 37 #include <stdbool.h> | 34 #include <stdbool.h> |
| 38 #include <stdio.h> | 35 #include <stdio.h> |
| 39 #include <stdlib.h> | 36 #include <stdlib.h> |
| 40 #include <string.h> | 37 #include <string.h> |
| 41 #include <time.h> | 38 #include <time.h> |
| 42 #include <unistd.h> | 39 #include <unistd.h> |
| 40 #include <sys/ptrace.h> | |
| 43 #include <sys/stat.h> | 41 #include <sys/stat.h> |
| 44 | 42 |
| 45 #include "tools/android/heap_profiler/heap_profiler.h" | 43 #include "tools/android/heap_profiler/heap_profiler.h" |
| 46 | 44 |
| 47 | 45 |
| 48 static void lseek_abs(int fd, size_t off); | 46 static void lseek_abs(int fd, size_t off); |
| 49 static void read_proc_cmdline(char* cmdline, int size); | 47 static void read_proc_cmdline(char* cmdline, int size); |
| 50 | 48 |
| 51 static int pid; | 49 static int pid; |
| 52 | 50 |
| 53 | 51 |
| 54 static int dump_process_heap( | 52 static int dump_process_heap( |
| 55 int mem_fd, | 53 int mem_fd, |
| 56 FILE* fmaps, | 54 FILE* fmaps, |
| 57 bool dump_also_allocs, | 55 bool dump_also_allocs, |
| 56 bool pedantic, // Enable pedantic consistency checks on memory counters. | |
| 58 char* comment) { | 57 char* comment) { |
| 59 HeapStats stats; | 58 HeapStats stats; |
| 60 time_t tm; | 59 time_t tm; |
| 61 char cmdline[512]; | 60 char cmdline[512]; |
| 62 | 61 |
| 63 tm = time(NULL); | 62 tm = time(NULL); |
| 64 read_proc_cmdline(cmdline, sizeof(cmdline)); | 63 read_proc_cmdline(cmdline, sizeof(cmdline)); |
| 65 | 64 |
| 66 // Look for the mmap which contains the HeapStats in the target process vmem. | 65 // Look for the mmap which contains the HeapStats in the target process vmem. |
| 67 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The | 66 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The |
| 68 // region furthermore starts with a magic marker to disambiguate. | 67 // region furthermore starts with a magic marker to disambiguate. |
| 69 bool stats_mmap_found = false; | 68 bool stats_mmap_found = false; |
| 70 for (;;) { | 69 for (;;) { |
| 71 char line[1024]; | 70 char line[1024]; |
| 72 if (fgets(line, sizeof(line), fmaps) == NULL) | 71 if (fgets(line, sizeof(line), fmaps) == NULL) |
| 73 break; | 72 break; |
| 74 | 73 |
| 75 uintptr_t start; | 74 uintptr_t start; |
| 76 uintptr_t end; | 75 uintptr_t end; |
| 77 char map_file[32]; | 76 char map_file[32]; |
| 78 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", | 77 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", |
| 79 &start, &end, map_file); | 78 &start, &end, map_file); |
| 80 const size_t size = end - start + 1; | 79 const size_t size = end - start + 1; |
| 81 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) | 80 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) |
| 82 continue; | 81 continue; |
| 83 | 82 |
| 84 // The mmap looks promising. Let's check for the magic marker. | 83 // The mmap looks promising. Let's check for the magic marker. |
| 85 lseek_abs(mem_fd, start); | 84 lseek_abs(mem_fd, start); |
| 86 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) | 85 ssize_t rsize = read(mem_fd, &stats, sizeof(stats)); |
| 87 continue; | 86 |
| 87 if (rsize == -1) { | |
| 88 perror("read"); | |
| 89 return -1; | |
| 90 } | |
| 91 | |
| 92 if (rsize < sizeof(stats)) | |
| 93 continue; // A false positive. | |
|
pasko
2014/09/10 12:09:18
this could be a short read, please check for EINTR
Primiano Tucci (use gerrit)
2014/09/10 13:17:35
Very hard this will happen (The Android framework
| |
| 88 | 94 |
| 89 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { | 95 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { |
| 90 stats_mmap_found = true; | 96 stats_mmap_found = true; |
| 91 break; | 97 break; |
| 92 } | 98 } |
| 93 } | 99 } |
| 94 | 100 |
| 95 if (!stats_mmap_found) { | 101 if (!stats_mmap_found) { |
| 96 fprintf(stderr, "Could not find the HeapStats area. " | 102 fprintf(stderr, "Could not find the HeapStats area. " |
| 97 "It looks like libheap_profiler is not loaded.\n"); | 103 "It looks like libheap_profiler is not loaded.\n"); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 143 ++dbg_counted_allocs; | 149 ++dbg_counted_allocs; |
| 144 | 150 |
| 145 if (prepend_trailing_comma) | 151 if (prepend_trailing_comma) |
| 146 printf(","); | 152 printf(","); |
| 147 prepend_trailing_comma = true; | 153 prepend_trailing_comma = true; |
| 148 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", | 154 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", |
| 149 alloc.start, alloc_size, alloc.flags, stack_idx); | 155 alloc.start, alloc_size, alloc.flags, stack_idx); |
| 150 } | 156 } |
| 151 printf("},\n"); | 157 printf("},\n"); |
| 152 | 158 |
| 153 if (dbg_counted_allocs != stats.num_allocs) { | 159 if (pedantic && dbg_counted_allocs != stats.num_allocs) { |
| 154 fprintf(stderr, | 160 fprintf(stderr, |
| 155 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | 161 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", |
| 156 dbg_counted_allocs, stats.num_allocs); | 162 dbg_counted_allocs, stats.num_allocs); |
| 157 return -1; | 163 return -1; |
| 158 } | 164 } |
| 159 | 165 |
| 160 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | 166 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| 161 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", | 167 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", |
| 162 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | 168 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| 163 return -1; | 169 return -1; |
| 164 } | 170 } |
| 165 } | 171 } |
| 166 | 172 |
| 167 // Dump the distinct stack traces. | 173 // Dump the distinct stack traces. |
| 168 printf(" \"stacks\": {"); | 174 printf(" \"stacks\": {"); |
| 169 prepend_trailing_comma = false; | 175 prepend_trailing_comma = false; |
| 170 dbg_counted_total_alloc_bytes = 0; | 176 dbg_counted_total_alloc_bytes = 0; |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 194 ++n; | 200 ++n; |
| 195 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) | 201 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) |
| 196 break; | 202 break; |
| 197 else | 203 else |
| 198 printf(","); | 204 printf(","); |
| 199 } | 205 } |
| 200 printf("]}"); | 206 printf("]}"); |
| 201 } | 207 } |
| 202 printf("}\n}\n"); | 208 printf("}\n}\n"); |
| 203 | 209 |
| 204 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | 210 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| 205 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", | 211 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", |
| 206 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | 212 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| 207 return -1; | 213 return -1; |
| 208 } | 214 } |
| 209 | 215 |
| 210 fflush(stdout); | 216 fflush(stdout); |
| 211 return 0; | 217 return 0; |
| 212 } | 218 } |
| 213 | 219 |
| 214 // If the dump is interrupted, resume the target process before exiting. | |
| 215 static void exit_handler() { | |
| 216 kill(pid, SIGCONT); | |
| 217 waitpid(pid, NULL, 0); | |
| 218 exit(-1); | |
| 219 } | |
| 220 | |
| 221 static bool freeze_process() { | |
| 222 if (kill(pid, SIGSTOP) != 0) { | |
| 223 fprintf(stderr, "Could not freeze the target process.\n"); | |
| 224 perror("kill"); | |
| 225 return false; | |
| 226 } | |
| 227 | |
| 228 signal(SIGPIPE, exit_handler); | |
| 229 signal(SIGINT, exit_handler); | |
| 230 return true; | |
| 231 } | |
| 232 | |
| 233 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large | 220 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large |
| 234 // files like /proc/X/mem on 64-bit. | 221 // files like /proc/X/mem on 64-bit. |
| 235 static void lseek_abs(int fd, size_t off) { | 222 static void lseek_abs(int fd, size_t off) { |
| 236 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) | 223 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) |
| 237 if (off <= OFF_T_MAX) { | 224 if (off <= OFF_T_MAX) { |
| 238 lseek(fd, (off_t) off, SEEK_SET); | 225 lseek(fd, (off_t) off, SEEK_SET); |
| 239 return; | 226 return; |
| 240 } | 227 } |
| 241 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); | 228 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); |
| 242 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); | 229 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 280 perror("read"); | 267 perror("read"); |
| 281 length = 0; | 268 length = 0; |
| 282 } | 269 } |
| 283 close(cmdline_fd); | 270 close(cmdline_fd); |
| 284 cmdline[length] = '\0'; | 271 cmdline[length] = '\0'; |
| 285 } | 272 } |
| 286 | 273 |
| 287 int main(int argc, char** argv) { | 274 int main(int argc, char** argv) { |
| 288 char c; | 275 char c; |
| 289 int ret = 0; | 276 int ret = 0; |
| 290 bool should_freeze_process = true; | |
| 291 bool dump_also_allocs = false; | 277 bool dump_also_allocs = false; |
| 278 bool pedantic = true; | |
| 292 char comment[1024] = { '\0' }; | 279 char comment[1024] = { '\0' }; |
| 293 | 280 |
| 294 while (((c = getopt(argc, argv, "nxc:")) & 0x80) == 0) { | 281 while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) { |
|
pasko
2014/09/10 12:09:18
Please provide a short human-readable description
Primiano Tucci (use gerrit)
2014/09/10 13:17:35
OK. I'll expand the usage string.
| |
| 295 switch (c) { | 282 switch (c) { |
| 296 case 'n': | |
| 297 should_freeze_process = false; | |
| 298 break; | |
| 299 case 'x': | 283 case 'x': |
| 300 dump_also_allocs = true; | 284 dump_also_allocs = true; |
| 301 break; | 285 break; |
| 286 case 'n': | |
| 287 pedantic = false; | |
| 288 break; | |
| 302 case 'c': | 289 case 'c': |
| 303 strlcpy(comment, optarg, sizeof(comment)); | 290 strlcpy(comment, optarg, sizeof(comment)); |
| 304 break; | 291 break; |
| 305 } | 292 } |
| 306 } | 293 } |
| 307 | 294 |
| 308 if (optind >= argc) { | 295 if (optind >= argc) { |
| 309 printf("Usage: %s [-n] [-x] [-c comment] pid\n", argv[0]); | 296 printf("Usage: %s [-n] [-x] [-c comment] pid\n", argv[0]); |
| 310 return -1; | 297 return -1; |
| 311 } | 298 } |
| 312 | 299 |
| 313 pid = atoi(argv[optind]); | 300 pid = atoi(argv[optind]); |
| 314 | 301 |
| 315 if (should_freeze_process && !freeze_process()) | 302 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { |
|
pasko
2014/09/10 12:09:18
ptrace interface is tricky and I am not a good eno
Primiano Tucci (use gerrit)
2014/09/10 13:17:35
Very hard to tell. However PTRACE_ATTACH is the wa
| |
| 303 perror("ptrace"); | |
| 316 return -1; | 304 return -1; |
| 305 } | |
| 317 | 306 |
| 318 // Wait for the process to actually freeze. | 307 // Wait for the process to actually freeze. |
| 319 waitpid(pid, NULL, 0); | 308 waitpid(pid, NULL, 0); |
| 320 | 309 |
| 321 int mem_fd = open_proc_mem_fd(); | 310 int mem_fd = open_proc_mem_fd(); |
| 322 if (mem_fd < 0) | 311 if (mem_fd < 0) |
| 323 ret = -1; | 312 ret = -1; |
| 324 | 313 |
| 325 FILE* fmaps = open_proc_maps(); | 314 FILE* fmaps = open_proc_maps(); |
| 326 if (fmaps == NULL) | 315 if (fmaps == NULL) |
| 327 ret = -1; | 316 ret = -1; |
| 328 | 317 |
| 329 if (ret == 0) | 318 if (ret == 0) |
| 330 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, comment); | 319 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment); |
| 331 | 320 |
| 332 if (should_freeze_process) | 321 ptrace(PTRACE_DETACH, pid, NULL, NULL); |
|
pasko
2014/09/10 12:09:18
ptrace manual page says:
"""
While being traced, t
pasko
2014/09/10 12:45:53
please disregard this comment, the quote from abov
Primiano Tucci (use gerrit)
2014/09/10 13:17:35
That is for the case of fork + TRACEME. In the cas
| |
| 333 kill(pid, SIGCONT); | |
| 334 | 322 |
| 335 // Cleanup. | 323 // Cleanup. |
| 336 fflush(stdout); | 324 fflush(stdout); |
| 337 close(mem_fd); | 325 close(mem_fd); |
| 338 fclose(fmaps); | 326 fclose(fmaps); |
| 339 return ret; | 327 return ret; |
| 340 } | 328 } |
| OLD | NEW |