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) | |
Dai Mikurube (NOT FULLTIME)
2014/07/02 04:49:52
What do you mean by "alloc" here? To me, "alloc" s
Primiano Tucci (use gerrit)
2014/07/02 07:43:28
Right, let me clarify. The short version is: an al
Dai Mikurube (NOT FULLTIME)
2014/07/02 09:39:32
Hmm, okay, but I think you shouldn't use the word
Primiano Tucci (use gerrit)
2014/07/02 09:49:53
Hmm agree. I'll reword it.
Dai Mikurube (NOT FULLTIME)
2014/07/02 10:04:58
Sure. I agree with the basic idea. My suggestion i
| |
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 which created the vma. | |
20 // | | | | |
21 // | | +-------> Last arg of heap_profiler_alloc(). | |
22 // | +----------------> Length of the VMA. | |
23 // +-----------------------------> Start address of the VMA (hex). | |
Dai Mikurube (NOT FULLTIME)
2014/07/02 04:49:52
Where does this start address point?
In my trial,
Primiano Tucci (use gerrit)
2014/07/02 07:43:28
I think it should be clear now. Very likely, that
| |
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_vmas = 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 VMA vma; | |
107 if (read(mem_fd, &vma, sizeof(vma)) != sizeof(vma)) { | |
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 (vma.start == 0 && vma.end == 0) | |
115 continue; | |
116 | |
117 if (vma.end < vma.start) { | |
118 fprintf(stderr, "ERROR: found inconsistent vma.\n"); | |
119 return -1; | |
120 } | |
121 | |
122 size_t vma_size = vma.end - vma.start + 1; | |
123 size_t stack_idx = ((uintptr_t) vma.st - (uintptr_t) stats.stack_traces) / | |
124 sizeof(StacktraceEntry); | |
125 dbg_counted_total_alloc_bytes += vma_size; | |
126 ++dbg_counted_vmas; | |
127 | |
128 if (prepend_trailing_comma) | |
129 printf(","); | |
130 prepend_trailing_comma = true; | |
131 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", | |
132 vma.start, vma_size, vma.flags, stack_idx); | |
133 } | |
134 printf("},\n"); | |
135 | |
136 if (dbg_counted_vmas != stats.num_allocs) { | |
137 fprintf(stderr, | |
138 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", | |
139 dbg_counted_vmas, stats.num_allocs); | |
140 return -1; | |
141 } | |
142 | |
143 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
144 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", | |
145 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
146 return -1; | |
147 } | |
148 } | |
149 | |
150 // Dump the distinct stack traces. | |
151 printf(" \"stacks\": {"); | |
152 prepend_trailing_comma = false; | |
153 dbg_counted_total_alloc_bytes = 0; | |
154 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); | |
155 for (i = 0; i < stats.max_stack_traces; ++i) { | |
156 StacktraceEntry st; | |
157 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { | |
158 fprintf(stderr, "ERROR: cannot read stack trace table\n"); | |
159 perror("read"); | |
160 return -1; | |
161 } | |
162 | |
163 // Skip empty (i.e. freed) entries. | |
164 if (st.alloc_bytes == 0) | |
165 continue; | |
166 | |
167 dbg_counted_total_alloc_bytes += st.alloc_bytes; | |
168 | |
169 if (prepend_trailing_comma) | |
170 printf(","); | |
171 prepend_trailing_comma = true; | |
172 | |
173 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); | |
174 size_t n = 0; | |
175 for (;;) { | |
176 printf("%" PRIuPTR, st.frames[n]); | |
177 ++n; | |
178 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) | |
179 break; | |
180 else | |
181 printf(","); | |
182 } | |
183 printf("]}"); | |
184 } | |
185 printf("}\n}\n"); | |
186 | |
187 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { | |
188 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", | |
189 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); | |
190 return -1; | |
191 } | |
192 | |
193 fflush(stdout); | |
194 return 0; | |
195 } | |
196 | |
197 // If the dump is interrupted, resume the target process before exiting. | |
198 static void exit_handler() { | |
199 kill(pid, SIGCONT); | |
200 waitpid(pid, NULL, 0); | |
201 exit(-1); | |
202 } | |
203 | |
204 static bool freeze_process() { | |
205 if (kill(pid, SIGSTOP) != 0) { | |
206 fprintf(stderr, "Could not freeze the target process.\n"); | |
207 perror("kill"); | |
208 return false; | |
209 } | |
210 | |
211 signal(SIGPIPE, exit_handler); | |
212 signal(SIGINT, exit_handler); | |
213 return true; | |
214 } | |
215 | |
216 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large | |
217 // files like /proc/X/mem on 64-bit. | |
218 static void lseek_abs(int fd, size_t off) { | |
219 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) | |
220 if (off <= OFF_T_MAX) { | |
221 lseek(fd, (off_t) off, SEEK_SET); | |
222 return; | |
223 } | |
224 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); | |
225 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); | |
226 } | |
227 | |
228 static int open_proc_mem_fd() { | |
229 char path[64]; | |
230 snprintf(path, sizeof(path), "/proc/%d/mem", pid); | |
231 int mem_fd = open(path, O_RDONLY); | |
232 if (mem_fd < 0) { | |
233 fprintf(stderr, "Could not attach to target process virtual memory.\n"); | |
234 perror("open"); | |
235 } | |
236 return mem_fd; | |
237 } | |
238 | |
239 static FILE* open_proc_maps() { | |
240 char path[64]; | |
241 snprintf(path, sizeof(path), "/proc/%d/maps", pid); | |
242 FILE* fmaps = fopen(path, "r"); | |
243 if (fmaps == NULL) { | |
244 fprintf(stderr, "Could not open %s.\n", path); | |
245 perror("fopen"); | |
246 } | |
247 return fmaps; | |
248 } | |
249 | |
250 int main(int argc, char** argv) { | |
251 char c; | |
252 int ret = 0; | |
253 bool should_freeze_process = true; | |
254 bool dump_also_allocs = false; | |
255 | |
256 | |
257 while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) { | |
258 switch (c) { | |
259 case 'n': | |
260 should_freeze_process = false; | |
261 break; | |
262 case 'x': | |
263 dump_also_allocs = true; | |
264 break; | |
265 } | |
266 } | |
267 | |
268 if (optind >= argc) { | |
269 printf("Usage: %s [-n] [-x] pid\n", argv[0]); | |
270 return -1; | |
271 } | |
272 | |
273 pid = atoi(argv[optind]); | |
274 | |
275 if (should_freeze_process && !freeze_process()) | |
276 return -1; | |
277 | |
278 // Wait for the process to actually freeze. | |
279 waitpid(pid, NULL, 0); | |
280 | |
281 int mem_fd = open_proc_mem_fd(); | |
282 if (mem_fd < 0) | |
283 ret = -1; | |
284 | |
285 FILE* fmaps = open_proc_maps(); | |
286 if (fmaps == NULL) | |
287 ret = -1; | |
288 | |
289 if (ret == 0) | |
290 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs); | |
291 | |
292 if (should_freeze_process) | |
293 kill(pid, SIGCONT); | |
294 | |
295 // Cleanup. | |
296 fflush(stdout); | |
297 close(mem_fd); | |
298 fclose(fmaps); | |
299 return ret; | |
300 } | |
OLD | NEW |