Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(303)

Side by Side Diff: tools/android/heap_profiler/heap_dump.c

Issue 553403002: [Android] libheap_profiler/heap_dump: use ptrace instead of SIGSTOP. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: handle signal interrupts Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
32 #include <errno.h>
34 #include <fcntl.h> 33 #include <fcntl.h>
35 #include <inttypes.h> 34 #include <inttypes.h>
36 #include <signal.h>
37 #include <stdbool.h> 35 #include <stdbool.h>
38 #include <stdio.h> 36 #include <stdio.h>
39 #include <stdlib.h> 37 #include <stdlib.h>
40 #include <string.h> 38 #include <string.h>
41 #include <time.h> 39 #include <time.h>
42 #include <unistd.h> 40 #include <unistd.h>
41 #include <sys/ptrace.h>
43 #include <sys/stat.h> 42 #include <sys/stat.h>
44 43
45 #include "tools/android/heap_profiler/heap_profiler.h" 44 #include "tools/android/heap_profiler/heap_profiler.h"
46 45
47 46
48 static void lseek_abs(int fd, size_t off); 47 static void lseek_abs(int fd, size_t off);
49 static void read_proc_cmdline(char* cmdline, int size); 48 static void read_proc_cmdline(char* cmdline, int size);
49 static ssize_t read_safe(int fd, void* buf, size_t count);
50 50
51 static int pid; 51 static int pid;
52 52
53 53
54 static int dump_process_heap( 54 static int dump_process_heap(
55 int mem_fd, 55 int mem_fd,
56 FILE* fmaps, 56 FILE* fmaps,
57 bool dump_also_allocs, 57 bool dump_also_allocs,
58 bool pedantic, // Enable pedantic consistency checks on memory counters.
58 char* comment) { 59 char* comment) {
59 HeapStats stats; 60 HeapStats stats;
60 time_t tm; 61 time_t tm;
61 char cmdline[512]; 62 char cmdline[512];
62 63
63 tm = time(NULL); 64 tm = time(NULL);
64 read_proc_cmdline(cmdline, sizeof(cmdline)); 65 read_proc_cmdline(cmdline, sizeof(cmdline));
65 66
66 // Look for the mmap which contains the HeapStats in the target process vmem. 67 // 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 68 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
68 // region furthermore starts with a magic marker to disambiguate. 69 // region furthermore starts with a magic marker to disambiguate.
69 bool stats_mmap_found = false; 70 bool stats_mmap_found = false;
70 for (;;) { 71 for (;;) {
71 char line[1024]; 72 char line[1024];
72 if (fgets(line, sizeof(line), fmaps) == NULL) 73 if (fgets(line, sizeof(line), fmaps) == NULL)
73 break; 74 break;
74 75
75 uintptr_t start; 76 uintptr_t start;
76 uintptr_t end; 77 uintptr_t end;
77 char map_file[32]; 78 char map_file[32];
78 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", 79 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
79 &start, &end, map_file); 80 &start, &end, map_file);
80 const size_t size = end - start + 1; 81 const size_t size = end - start + 1;
81 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) 82 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
82 continue; 83 continue;
83 84
84 // The mmap looks promising. Let's check for the magic marker. 85 // The mmap looks promising. Let's check for the magic marker.
85 lseek_abs(mem_fd, start); 86 lseek_abs(mem_fd, start);
86 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) 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))
87 continue; 95 continue;
88 96
89 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { 97 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
90 stats_mmap_found = true; 98 stats_mmap_found = true;
91 break; 99 break;
92 } 100 }
93 } 101 }
94 102
95 if (!stats_mmap_found) { 103 if (!stats_mmap_found) {
96 fprintf(stderr, "Could not find the HeapStats area. " 104 fprintf(stderr, "Could not find the HeapStats area. "
(...skipping 16 matching lines...) Expand all
113 size_t dbg_counted_total_alloc_bytes = 0; 121 size_t dbg_counted_total_alloc_bytes = 0;
114 bool prepend_trailing_comma = false; // JSON syntax, I hate you. 122 bool prepend_trailing_comma = false; // JSON syntax, I hate you.
115 uint32_t i; 123 uint32_t i;
116 124
117 // Dump the optional allocation table. 125 // Dump the optional allocation table.
118 if (dump_also_allocs) { 126 if (dump_also_allocs) {
119 printf(" \"allocs\": {"); 127 printf(" \"allocs\": {");
120 lseek_abs(mem_fd, (uintptr_t) stats.allocs); 128 lseek_abs(mem_fd, (uintptr_t) stats.allocs);
121 for (i = 0; i < stats.max_allocs; ++i) { 129 for (i = 0; i < stats.max_allocs; ++i) {
122 Alloc alloc; 130 Alloc alloc;
123 if (read(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) { 131 if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) {
124 fprintf(stderr, "ERROR: cannot read allocation table\n"); 132 fprintf(stderr, "ERROR: cannot read allocation table\n");
125 perror("read"); 133 perror("read");
126 return -1; 134 return -1;
127 } 135 }
128 136
129 // Skip empty (i.e. freed) entries. 137 // Skip empty (i.e. freed) entries.
130 if (alloc.start == 0 && alloc.end == 0) 138 if (alloc.start == 0 && alloc.end == 0)
131 continue; 139 continue;
132 140
133 if (alloc.end < alloc.start) { 141 if (alloc.end < alloc.start) {
134 fprintf(stderr, "ERROR: found inconsistent alloc.\n"); 142 fprintf(stderr, "ERROR: found inconsistent alloc.\n");
135 return -1; 143 return -1;
136 } 144 }
137 145
138 size_t alloc_size = alloc.end - alloc.start + 1; 146 size_t alloc_size = alloc.end - alloc.start + 1;
139 size_t stack_idx = ( 147 size_t stack_idx = (
140 (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) / 148 (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) /
141 sizeof(StacktraceEntry); 149 sizeof(StacktraceEntry);
142 dbg_counted_total_alloc_bytes += alloc_size; 150 dbg_counted_total_alloc_bytes += alloc_size;
143 ++dbg_counted_allocs; 151 ++dbg_counted_allocs;
144 152
145 if (prepend_trailing_comma) 153 if (prepend_trailing_comma)
146 printf(","); 154 printf(",");
147 prepend_trailing_comma = true; 155 prepend_trailing_comma = true;
148 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", 156 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}",
149 alloc.start, alloc_size, alloc.flags, stack_idx); 157 alloc.start, alloc_size, alloc.flags, stack_idx);
150 } 158 }
151 printf("},\n"); 159 printf("},\n");
152 160
153 if (dbg_counted_allocs != stats.num_allocs) { 161 if (pedantic && dbg_counted_allocs != stats.num_allocs) {
154 fprintf(stderr, 162 fprintf(stderr,
155 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", 163 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
156 dbg_counted_allocs, stats.num_allocs); 164 dbg_counted_allocs, stats.num_allocs);
157 return -1; 165 return -1;
158 } 166 }
159 167
160 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { 168 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
161 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", 169 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
162 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); 170 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
163 return -1; 171 return -1;
164 } 172 }
165 } 173 }
166 174
167 // Dump the distinct stack traces. 175 // Dump the distinct stack traces.
168 printf(" \"stacks\": {"); 176 printf(" \"stacks\": {");
169 prepend_trailing_comma = false; 177 prepend_trailing_comma = false;
170 dbg_counted_total_alloc_bytes = 0; 178 dbg_counted_total_alloc_bytes = 0;
171 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); 179 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
172 for (i = 0; i < stats.max_stack_traces; ++i) { 180 for (i = 0; i < stats.max_stack_traces; ++i) {
173 StacktraceEntry st; 181 StacktraceEntry st;
174 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { 182 if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) {
175 fprintf(stderr, "ERROR: cannot read stack trace table\n"); 183 fprintf(stderr, "ERROR: cannot read stack trace table\n");
176 perror("read"); 184 perror("read");
177 return -1; 185 return -1;
178 } 186 }
179 187
180 // Skip empty (i.e. freed) entries. 188 // Skip empty (i.e. freed) entries.
181 if (st.alloc_bytes == 0) 189 if (st.alloc_bytes == 0)
182 continue; 190 continue;
183 191
184 dbg_counted_total_alloc_bytes += st.alloc_bytes; 192 dbg_counted_total_alloc_bytes += st.alloc_bytes;
185 193
186 if (prepend_trailing_comma) 194 if (prepend_trailing_comma)
187 printf(","); 195 printf(",");
188 prepend_trailing_comma = true; 196 prepend_trailing_comma = true;
189 197
190 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); 198 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes);
191 size_t n = 0; 199 size_t n = 0;
192 for (;;) { 200 for (;;) {
193 printf("%" PRIuPTR, st.frames[n]); 201 printf("%" PRIuPTR, st.frames[n]);
194 ++n; 202 ++n;
195 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) 203 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
196 break; 204 break;
197 else 205 else
198 printf(","); 206 printf(",");
199 } 207 }
200 printf("]}"); 208 printf("]}");
201 } 209 }
202 printf("}\n}\n"); 210 printf("}\n}\n");
203 211
204 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { 212 if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
205 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", 213 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
206 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); 214 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
207 return -1; 215 return -1;
208 } 216 }
209 217
210 fflush(stdout); 218 fflush(stdout);
211 return 0; 219 return 0;
212 } 220 }
213 221
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 222 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large
234 // files like /proc/X/mem on 64-bit. 223 // files like /proc/X/mem on 64-bit.
235 static void lseek_abs(int fd, size_t off) { 224 static void lseek_abs(int fd, size_t off) {
236 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) 225 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
237 if (off <= OFF_T_MAX) { 226 if (off <= OFF_T_MAX) {
238 lseek(fd, (off_t) off, SEEK_SET); 227 lseek(fd, (off_t) off, SEEK_SET);
239 return; 228 return;
240 } 229 }
241 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); 230 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
242 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); 231 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
243 } 232 }
244 233
234 static ssize_t read_safe(int fd, void* buf, size_t count) {
235 ssize_t res;
236 do {
237 res = read(fd, buf, count);
238 } while(res == 1 && errno == EINTR);
pasko 2014/09/10 15:25:23 s/1/-1/ And it still can be a short read. Take a
Primiano Tucci (use gerrit) 2014/09/10 15:42:16 That is veeeeeeery unlikely to happen when reading
pasko 2014/09/10 16:09:27 yes, it's unlikely, and your tool is unlikely to b
239 return res;
240 }
241
245 static int open_proc_mem_fd() { 242 static int open_proc_mem_fd() {
246 char path[64]; 243 char path[64];
247 snprintf(path, sizeof(path), "/proc/%d/mem", pid); 244 snprintf(path, sizeof(path), "/proc/%d/mem", pid);
248 int mem_fd = open(path, O_RDONLY); 245 int mem_fd = open(path, O_RDONLY);
249 if (mem_fd < 0) { 246 if (mem_fd < 0) {
250 fprintf(stderr, "Could not attach to target process virtual memory.\n"); 247 fprintf(stderr, "Could not attach to target process virtual memory.\n");
251 perror("open"); 248 perror("open");
252 } 249 }
253 return mem_fd; 250 return mem_fd;
254 } 251 }
(...skipping 12 matching lines...) Expand all
267 static void read_proc_cmdline(char* cmdline, int size) { 264 static void read_proc_cmdline(char* cmdline, int size) {
268 char path[64]; 265 char path[64];
269 snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); 266 snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
270 int cmdline_fd = open(path, O_RDONLY); 267 int cmdline_fd = open(path, O_RDONLY);
271 if (cmdline_fd < 0) { 268 if (cmdline_fd < 0) {
272 fprintf(stderr, "Could not open %s.\n", path); 269 fprintf(stderr, "Could not open %s.\n", path);
273 perror("open"); 270 perror("open");
274 cmdline[0] = '\0'; 271 cmdline[0] = '\0';
275 return; 272 return;
276 } 273 }
277 int length = read(cmdline_fd, cmdline, size); 274 int length = read_safe(cmdline_fd, cmdline, size);
278 if (length < 0) { 275 if (length < 0) {
279 fprintf(stderr, "Could not read %s.\n", path); 276 fprintf(stderr, "Could not read %s.\n", path);
280 perror("read"); 277 perror("read");
281 length = 0; 278 length = 0;
282 } 279 }
283 close(cmdline_fd); 280 close(cmdline_fd);
284 cmdline[length] = '\0'; 281 cmdline[length] = '\0';
285 } 282 }
286 283
287 int main(int argc, char** argv) { 284 int main(int argc, char** argv) {
288 char c; 285 char c;
289 int ret = 0; 286 int ret = 0;
290 bool should_freeze_process = true;
291 bool dump_also_allocs = false; 287 bool dump_also_allocs = false;
288 bool pedantic = true;
292 char comment[1024] = { '\0' }; 289 char comment[1024] = { '\0' };
293 290
294 while (((c = getopt(argc, argv, "nxc:")) & 0x80) == 0) { 291 while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) {
295 switch (c) { 292 switch (c) {
296 case 'n':
297 should_freeze_process = false;
298 break;
299 case 'x': 293 case 'x':
300 dump_also_allocs = true; 294 dump_also_allocs = true;
301 break; 295 break;
296 case 'n':
297 pedantic = false;
298 break;
302 case 'c': 299 case 'c':
303 strlcpy(comment, optarg, sizeof(comment)); 300 strlcpy(comment, optarg, sizeof(comment));
304 break; 301 break;
305 } 302 }
306 } 303 }
307 304
308 if (optind >= argc) { 305 if (optind >= argc) {
309 printf("Usage: %s [-n] [-x] [-c comment] pid\n", argv[0]); 306 printf("Usage: %s [-n] [-x] [-c comment] pid\n"
307 " -n: Skip pedantic checks on dump consistency.\n"
308 " -x: Extended dump, includes individual allocations.\n"
309 " -c: Appends the given comment to the JSON dump.\n",
310 argv[0]);
310 return -1; 311 return -1;
311 } 312 }
312 313
313 pid = atoi(argv[optind]); 314 pid = atoi(argv[optind]);
314 315
315 if (should_freeze_process && !freeze_process()) 316 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
317 perror("ptrace");
316 return -1; 318 return -1;
319 }
317 320
318 // Wait for the process to actually freeze. 321 // Wait for the process to actually freeze.
319 waitpid(pid, NULL, 0); 322 waitpid(pid, NULL, 0);
320 323
321 int mem_fd = open_proc_mem_fd(); 324 int mem_fd = open_proc_mem_fd();
322 if (mem_fd < 0) 325 if (mem_fd < 0)
323 ret = -1; 326 ret = -1;
324 327
325 FILE* fmaps = open_proc_maps(); 328 FILE* fmaps = open_proc_maps();
326 if (fmaps == NULL) 329 if (fmaps == NULL)
327 ret = -1; 330 ret = -1;
328 331
329 if (ret == 0) 332 if (ret == 0)
330 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, comment); 333 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment);
331 334
332 if (should_freeze_process) 335 ptrace(PTRACE_DETACH, pid, NULL, NULL);
333 kill(pid, SIGCONT);
334 336
335 // Cleanup. 337 // Cleanup.
336 fflush(stdout); 338 fflush(stdout);
337 close(mem_fd); 339 close(mem_fd);
338 fclose(fmaps); 340 fclose(fmaps);
339 return ret; 341 return ret;
340 } 342 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698