• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
dump_process_heap(int mem_fd,FILE * fmaps,bool dump_also_allocs,bool pedantic,char * comment)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.
lseek_abs(int fd,size_t off)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 
read_safe(int fd,void * buf,size_t count)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 
open_proc_mem_fd()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 
open_proc_maps()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 
read_proc_cmdline(char * cmdline,int size)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 
main(int argc,char ** argv)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