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 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 <stddef.h> | |
37 #include <stdint.h> | |
38 #include <stdio.h> | |
39 #include <stdlib.h> | |
40 #include <string.h> | |
41 #include <sys/ptrace.h> | |
42 #include <sys/stat.h> | |
43 #include <sys/wait.h> | |
44 #include <time.h> | |
45 #include <unistd.h> | |
46 | |
47 #include "tools/android/heap_profiler/heap_profiler.h" | |
48 | |
49 static void lseek_abs(int fd, size_t off); | |
50 static void read_proc_cmdline(char* cmdline, int size); | |
51 static ssize_t read_safe(int fd, void* buf, size_t count); | |
52 | |
53 static int pid; | |
54 | |
55 | |
56 static int dump_process_heap( | |
57 int mem_fd, | |
58 FILE* fmaps, | |
59 bool dump_also_allocs, | |
60 bool pedantic, // Enable pedantic consistency checks on memory counters. | |
61 char* comment) { | |
62 HeapStats stats; | |
63 time_t tm; | |
64 char cmdline[512]; | |
65 | |
66 tm = time(NULL); | |
67 read_proc_cmdline(cmdline, sizeof(cmdline)); | |
68 | |
69 // Look for the mmap which contains the HeapStats in the target process vmem. | |
70 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The | |
71 // region furthermore starts with a magic marker to disambiguate. | |
72 bool stats_mmap_found = false; | |
73 for (;;) { | |
74 char line[1024]; | |
75 if (fgets(line, sizeof(line), fmaps) == NULL) | |
76 break; | |
77 | |
78 uintptr_t start; | |
79 uintptr_t end; | |
80 char map_file[32]; | |
81 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", | |
82 &start, &end, map_file); | |
83 const size_t size = end - start + 1; | |
84 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) | |
85 continue; | |
86 | |
87 // The mmap looks promising. Let's check for the magic marker. | |
88 lseek_abs(mem_fd, start); | |
89 ssize_t rsize = read_safe(mem_fd, &stats, sizeof(stats)); | |
90 | |
91 if (rsize == -1) { | |
92 perror("read"); | |
93 return -1; | |
94 } | |
95 | |
96 if (rsize < sizeof(stats)) | |
97 continue; | |
98 | |
99 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { | |
100 stats_mmap_found = true; | |
101 break; | |
102 } | |
103 } | |
104 | |
105 if (!stats_mmap_found) { | |
106 fprintf(stderr, "Could not find the HeapStats area. " | |
107 "It looks like libheap_profiler is not loaded.\n"); | |
108 return -1; | |
109 } | |
110 | |
111 // Print JSON-formatted output. | |
112 printf("{\n"); | |
113 printf(" \"pid\": %d,\n", pid); | |
114 printf(" \"time\": %ld,\n", tm); | |
115 printf(" \"comment\": \"%s\",\n", comment); | |
116 printf(" \"cmdline\": \"%s\",\n", cmdline); | |
117 printf(" \"pagesize\": %d,\n", getpagesize()); | |
118 printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes); | |
119 printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs); | |
120 printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces); | |
121 | |
122 uint32_t dbg_counted_allocs = 0; | |
123 size_t dbg_counted_total_alloc_bytes = 0; | |
124 bool prepend_trailing_comma = false; // JSON syntax, I hate you. | |
125 uint32_t i; | |
126 | |
127 // Dump the optional allocation table. | |
128 if (dump_also_allocs) { | |
129 printf(" \"allocs\": {"); | |
130 lseek_abs(mem_fd, (uintptr_t) stats.allocs); | |
131 for (i = 0; i < stats.max_allocs; ++i) { | |
132 Alloc alloc; | |
133 if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) { | |
134 fprintf(stderr, "ERROR: cannot read allocation table\n"); | |
135 perror("read"); | |
136 return -1; | |
137 } | |
138 | |
139 // Skip empty (i.e. freed) entries. | |
140 if (alloc.start == 0 && alloc.end == 0) | |
141 continue; | |
142 | |
143 if (alloc.end < alloc.start) { | |
144 fprintf(stderr, "ERROR: found inconsistent alloc.\n"); | |
145 return -1; | |
146 } | |
147 | |
148 size_t alloc_size = alloc.end - alloc.start + 1; | |
149 size_t stack_idx = ( | |
150 (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) / | |
151 sizeof(StacktraceEntry); | |
152 dbg_counted_total_alloc_bytes += alloc_size; | |
153 ++dbg_counted_allocs; | |
154 | |
155 if (prepend_trailing_comma) | |
156 printf(","); | |
157 prepend_trailing_comma = true; | |
158 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", | |
159 alloc.start, alloc_size, alloc.flags, stack_idx); | |
160 } | |
161 printf("},\n"); | |
162 | |
163 if (pedantic && dbg_counted_allocs != stats.num_allocs) { | |
164 fprintf(stderr, | |
165 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | |
166 dbg_counted_allocs, stats.num_allocs); | |
167 return -1; | |
168 } | |
169 | |
170 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
171 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", | |
172 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
173 return -1; | |
174 } | |
175 } | |
176 | |
177 // Dump the distinct stack traces. | |
178 printf(" \"stacks\": {"); | |
179 prepend_trailing_comma = false; | |
180 dbg_counted_total_alloc_bytes = 0; | |
181 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); | |
182 for (i = 0; i < stats.max_stack_traces; ++i) { | |
183 StacktraceEntry st; | |
184 if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) { | |
185 fprintf(stderr, "ERROR: cannot read stack trace table\n"); | |
186 perror("read"); | |
187 return -1; | |
188 } | |
189 | |
190 // Skip empty (i.e. freed) entries. | |
191 if (st.alloc_bytes == 0) | |
192 continue; | |
193 | |
194 dbg_counted_total_alloc_bytes += st.alloc_bytes; | |
195 | |
196 if (prepend_trailing_comma) | |
197 printf(","); | |
198 prepend_trailing_comma = true; | |
199 | |
200 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); | |
201 size_t n = 0; | |
202 for (;;) { | |
203 printf("%" PRIuPTR, st.frames[n]); | |
204 ++n; | |
205 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) | |
206 break; | |
207 else | |
208 printf(","); | |
209 } | |
210 printf("]}"); | |
211 } | |
212 printf("}\n}\n"); | |
213 | |
214 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
215 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", | |
216 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
217 return -1; | |
218 } | |
219 | |
220 fflush(stdout); | |
221 return 0; | |
222 } | |
223 | |
224 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large | |
225 // files like /proc/X/mem on 64-bit. | |
226 static void lseek_abs(int fd, size_t off) { | |
227 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) | |
228 if (off <= OFF_T_MAX) { | |
229 lseek(fd, (off_t) off, SEEK_SET); | |
230 return; | |
231 } | |
232 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); | |
233 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); | |
234 } | |
235 | |
236 static ssize_t read_safe(int fd, void* buf, size_t count) { | |
237 ssize_t res; | |
238 size_t bytes_read = 0; | |
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 } | |
OLD | NEW |