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