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.
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:
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.
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.
19 // | | +-------> Flags (last arg of heap_profiler_alloc).
20 // | +----------------> Length of the Alloc.
21 // +-----------------------------> Start address of the Alloc (hex).
25 // "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]},
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.
41 #include <sys/ptrace.h>
44 #include "tools/android/heap_profiler/heap_profiler.h"
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
);
54 static int dump_process_heap(
57 bool dump_also_allocs
,
58 bool pedantic
, // Enable pedantic consistency checks on memory counters.
65 read_proc_cmdline(cmdline
, sizeof(cmdline
));
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;
73 if (fgets(line
, sizeof(line
), fmaps
) == NULL
)
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
))
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
));
94 if (rsize
< sizeof(stats
))
97 if (stats
.magic_start
== HEAP_PROFILER_MAGIC_MARKER
) {
98 stats_mmap_found
= true;
103 if (!stats_mmap_found
) {
104 fprintf(stderr
, "Could not find the HeapStats area. "
105 "It looks like libheap_profiler is not loaded.\n");
109 // Print JSON-formatted output.
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
);
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.
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
) {
131 if (read_safe(mem_fd
, &alloc
, sizeof(alloc
)) != sizeof(alloc
)) {
132 fprintf(stderr
, "ERROR: cannot read allocation table\n");
137 // Skip empty (i.e. freed) entries.
138 if (alloc
.start
== 0 && alloc
.end
== 0)
141 if (alloc
.end
< alloc
.start
) {
142 fprintf(stderr
, "ERROR: found inconsistent alloc.\n");
146 size_t alloc_size
= alloc
.end
- alloc
.start
+ 1;
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
;
153 if (prepend_trailing_comma
)
155 prepend_trailing_comma
= true;
156 printf("\"%"PRIxPTR
"\": {\"l
\": %zu
, \"f
\": %"PRIu32", \"s
\": \"%zx
\"}",
157 alloc.start, alloc_size, alloc.flags, stack_idx);
161 if (pedantic && dbg_counted_allocs != stats.num_allocs) {
163 "ERROR
: inconsistent alloc
count (%"PRIu32" vs
%"PRIu32").\n",
164 dbg_counted_allocs, stats.num_allocs);
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);
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) {
182 if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) {
183 fprintf(stderr, "ERROR
: cannot read stack trace table
\n");
188 // Skip empty (i.e. freed) entries.
189 if (st.alloc_bytes == 0)
192 dbg_counted_total_alloc_bytes += st.alloc_bytes;
194 if (prepend_trailing_comma)
196 prepend_trailing_comma = true;
198 printf("\"%"PRIx32"\":{\"l
\": %zu
, \"f
\": [", i, st.alloc_bytes);
201 printf("%" PRIuPTR, st.frames[n]);
203 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
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);
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);
230 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
231 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
234 static ssize_t read_safe(int fd, void* buf, size_t count) {
236 size_t bytes_read = 0;
241 res = read(fd, buf + bytes_read, count - bytes_read);
242 } while (res == -1 && errno == EINTR);
246 } while (bytes_read < count);
247 return bytes_read ? bytes_read : res;
250 static int open_proc_mem_fd() {
252 snprintf(path, sizeof(path), "/proc
/%d
/mem
", pid);
253 int mem_fd = open(path, O_RDONLY);
255 fprintf(stderr, "Could
not attach to target process
virtual memory
.\n");
261 static FILE* open_proc_maps() {
263 snprintf(path, sizeof(path), "/proc
/%d
/maps
", pid);
264 FILE* fmaps = fopen(path, "r
");
266 fprintf(stderr, "Could
not open
%s
.\n", path);
272 static void read_proc_cmdline(char* cmdline, int size) {
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);
282 int length = read_safe(cmdline_fd, cmdline, size);
284 fprintf(stderr, "Could
not read
%s
.\n", path);
289 cmdline[length] = '\0';
292 int main(int argc, char** argv) {
295 bool dump_also_allocs = false;
296 bool pedantic = true;
297 char comment[1024] = { '\0' };
299 while (((c = getopt(argc, argv, "xnc
:")) & 0x80) == 0) {
302 dump_also_allocs = true;
308 strlcpy(comment, optarg, sizeof(comment));
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",
322 pid = atoi(argv[optind]);
324 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
329 // Wait for the process to actually freeze.
330 waitpid(pid, NULL, 0);
332 int mem_fd = open_proc_mem_fd();
336 FILE* fmaps = open_proc_maps();
341 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment);
343 ptrace(PTRACE_DETACH, pid, NULL, NULL);