mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
351 lines
10 KiB
C
351 lines
10 KiB
C
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// The client dump tool for libheap_profiler. It attaches to a process (given
|
|
// its pid) and dumps all the libheap_profiler tracking information in JSON.
|
|
// The JSON output looks like this:
|
|
// {
|
|
// "total_allocated": 908748493, # Total bytes allocated and not freed.
|
|
// "num_allocs": 37542, # Number of allocations.
|
|
// "num_stacks": 3723, # Number of allocation call-sites.
|
|
// "allocs": # Optional. Printed only with the -x arg.
|
|
// {
|
|
// "beef1234": {"l": 17, "f": 1, "s": "1a"},
|
|
// ^ ^ ^ ^ Index of the corresponding entry in the
|
|
// | | | next "stacks" section. Essentially a ref
|
|
// | | | to the call site that created the alloc.
|
|
// | | |
|
|
// | | +-------> Flags (last arg of heap_profiler_alloc).
|
|
// | +----------------> Length of the Alloc.
|
|
// +-----------------------------> Start address of the Alloc (hex).
|
|
// },
|
|
// "stacks":
|
|
// {
|
|
// "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]},
|
|
// ^ ^ ^
|
|
// | | +-----> Stack frames (absolute virtual addresses).
|
|
// | +--------------> Bytes allocated and not freed by the call site.
|
|
// +---------------------> Index of the entry (as for "allocs" xref).
|
|
// Indexes are hex and might not be monotonic.
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "tools/android/heap_profiler/heap_profiler.h"
|
|
|
|
|
|
static void lseek_abs(int fd, size_t off);
|
|
static void read_proc_cmdline(char* cmdline, int size);
|
|
static ssize_t read_safe(int fd, void* buf, size_t count);
|
|
|
|
static int pid;
|
|
|
|
|
|
static int dump_process_heap(
|
|
int mem_fd,
|
|
FILE* fmaps,
|
|
bool dump_also_allocs,
|
|
bool pedantic, // Enable pedantic consistency checks on memory counters.
|
|
char* comment) {
|
|
HeapStats stats;
|
|
time_t tm;
|
|
char cmdline[512];
|
|
|
|
tm = time(NULL);
|
|
read_proc_cmdline(cmdline, sizeof(cmdline));
|
|
|
|
// Look for the mmap which contains the HeapStats in the target process vmem.
|
|
// On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
|
|
// region furthermore starts with a magic marker to disambiguate.
|
|
bool stats_mmap_found = false;
|
|
for (;;) {
|
|
char line[1024];
|
|
if (fgets(line, sizeof(line), fmaps) == NULL)
|
|
break;
|
|
|
|
uintptr_t start;
|
|
uintptr_t end;
|
|
char map_file[32];
|
|
int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
|
|
&start, &end, map_file);
|
|
const size_t size = end - start + 1;
|
|
if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
|
|
continue;
|
|
|
|
// The mmap looks promising. Let's check for the magic marker.
|
|
lseek_abs(mem_fd, start);
|
|
ssize_t rsize = read_safe(mem_fd, &stats, sizeof(stats));
|
|
|
|
if (rsize == -1) {
|
|
perror("read");
|
|
return -1;
|
|
}
|
|
|
|
if (rsize < sizeof(stats))
|
|
continue;
|
|
|
|
if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
|
|
stats_mmap_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!stats_mmap_found) {
|
|
fprintf(stderr, "Could not find the HeapStats area. "
|
|
"It looks like libheap_profiler is not loaded.\n");
|
|
return -1;
|
|
}
|
|
|
|
// Print JSON-formatted output.
|
|
printf("{\n");
|
|
printf(" \"pid\": %d,\n", pid);
|
|
printf(" \"time\": %ld,\n", tm);
|
|
printf(" \"comment\": \"%s\",\n", comment);
|
|
printf(" \"cmdline\": \"%s\",\n", cmdline);
|
|
printf(" \"pagesize\": %d,\n", getpagesize());
|
|
printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes);
|
|
printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs);
|
|
printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces);
|
|
|
|
uint32_t dbg_counted_allocs = 0;
|
|
size_t dbg_counted_total_alloc_bytes = 0;
|
|
bool prepend_trailing_comma = false; // JSON syntax, I hate you.
|
|
uint32_t i;
|
|
|
|
// Dump the optional allocation table.
|
|
if (dump_also_allocs) {
|
|
printf(" \"allocs\": {");
|
|
lseek_abs(mem_fd, (uintptr_t) stats.allocs);
|
|
for (i = 0; i < stats.max_allocs; ++i) {
|
|
Alloc alloc;
|
|
if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) {
|
|
fprintf(stderr, "ERROR: cannot read allocation table\n");
|
|
perror("read");
|
|
return -1;
|
|
}
|
|
|
|
// Skip empty (i.e. freed) entries.
|
|
if (alloc.start == 0 && alloc.end == 0)
|
|
continue;
|
|
|
|
if (alloc.end < alloc.start) {
|
|
fprintf(stderr, "ERROR: found inconsistent alloc.\n");
|
|
return -1;
|
|
}
|
|
|
|
size_t alloc_size = alloc.end - alloc.start + 1;
|
|
size_t stack_idx = (
|
|
(uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) /
|
|
sizeof(StacktraceEntry);
|
|
dbg_counted_total_alloc_bytes += alloc_size;
|
|
++dbg_counted_allocs;
|
|
|
|
if (prepend_trailing_comma)
|
|
printf(",");
|
|
prepend_trailing_comma = true;
|
|
printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}",
|
|
alloc.start, alloc_size, alloc.flags, stack_idx);
|
|
}
|
|
printf("},\n");
|
|
|
|
if (pedantic && dbg_counted_allocs != stats.num_allocs) {
|
|
fprintf(stderr,
|
|
"ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
|
|
dbg_counted_allocs, stats.num_allocs);
|
|
return -1;
|
|
}
|
|
|
|
if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
|
|
fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
|
|
dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Dump the distinct stack traces.
|
|
printf(" \"stacks\": {");
|
|
prepend_trailing_comma = false;
|
|
dbg_counted_total_alloc_bytes = 0;
|
|
lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
|
|
for (i = 0; i < stats.max_stack_traces; ++i) {
|
|
StacktraceEntry st;
|
|
if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) {
|
|
fprintf(stderr, "ERROR: cannot read stack trace table\n");
|
|
perror("read");
|
|
return -1;
|
|
}
|
|
|
|
// Skip empty (i.e. freed) entries.
|
|
if (st.alloc_bytes == 0)
|
|
continue;
|
|
|
|
dbg_counted_total_alloc_bytes += st.alloc_bytes;
|
|
|
|
if (prepend_trailing_comma)
|
|
printf(",");
|
|
prepend_trailing_comma = true;
|
|
|
|
printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes);
|
|
size_t n = 0;
|
|
for (;;) {
|
|
printf("%" PRIuPTR, st.frames[n]);
|
|
++n;
|
|
if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
|
|
break;
|
|
else
|
|
printf(",");
|
|
}
|
|
printf("]}");
|
|
}
|
|
printf("}\n}\n");
|
|
|
|
if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
|
|
fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
|
|
dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
|
|
return -1;
|
|
}
|
|
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
|
|
// Unfortunately lseek takes a *signed* offset, which is unsuitable for large
|
|
// files like /proc/X/mem on 64-bit.
|
|
static void lseek_abs(int fd, size_t off) {
|
|
#define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
|
|
if (off <= OFF_T_MAX) {
|
|
lseek(fd, (off_t) off, SEEK_SET);
|
|
return;
|
|
}
|
|
lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
|
|
lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
|
|
}
|
|
|
|
static ssize_t read_safe(int fd, void* buf, size_t count) {
|
|
ssize_t res;
|
|
size_t bytes_read = 0;
|
|
if (count < 0)
|
|
return -1;
|
|
do {
|
|
do {
|
|
res = read(fd, buf + bytes_read, count - bytes_read);
|
|
} while (res == -1 && errno == EINTR);
|
|
if (res <= 0)
|
|
break;
|
|
bytes_read += res;
|
|
} while (bytes_read < count);
|
|
return bytes_read ? bytes_read : res;
|
|
}
|
|
|
|
static int open_proc_mem_fd() {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%d/mem", pid);
|
|
int mem_fd = open(path, O_RDONLY);
|
|
if (mem_fd < 0) {
|
|
fprintf(stderr, "Could not attach to target process virtual memory.\n");
|
|
perror("open");
|
|
}
|
|
return mem_fd;
|
|
}
|
|
|
|
static FILE* open_proc_maps() {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
|
|
FILE* fmaps = fopen(path, "r");
|
|
if (fmaps == NULL) {
|
|
fprintf(stderr, "Could not open %s.\n", path);
|
|
perror("fopen");
|
|
}
|
|
return fmaps;
|
|
}
|
|
|
|
static void read_proc_cmdline(char* cmdline, int size) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
|
|
int cmdline_fd = open(path, O_RDONLY);
|
|
if (cmdline_fd < 0) {
|
|
fprintf(stderr, "Could not open %s.\n", path);
|
|
perror("open");
|
|
cmdline[0] = '\0';
|
|
return;
|
|
}
|
|
int length = read_safe(cmdline_fd, cmdline, size);
|
|
if (length < 0) {
|
|
fprintf(stderr, "Could not read %s.\n", path);
|
|
perror("read");
|
|
length = 0;
|
|
}
|
|
close(cmdline_fd);
|
|
cmdline[length] = '\0';
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
char c;
|
|
int ret = 0;
|
|
bool dump_also_allocs = false;
|
|
bool pedantic = true;
|
|
char comment[1024] = { '\0' };
|
|
|
|
while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) {
|
|
switch (c) {
|
|
case 'x':
|
|
dump_also_allocs = true;
|
|
break;
|
|
case 'n':
|
|
pedantic = false;
|
|
break;
|
|
case 'c':
|
|
strlcpy(comment, optarg, sizeof(comment));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
printf("Usage: %s [-n] [-x] [-c comment] pid\n"
|
|
" -n: Skip pedantic checks on dump consistency.\n"
|
|
" -x: Extended dump, includes individual allocations.\n"
|
|
" -c: Appends the given comment to the JSON dump.\n",
|
|
argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
pid = atoi(argv[optind]);
|
|
|
|
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
|
|
perror("ptrace");
|
|
return -1;
|
|
}
|
|
|
|
// Wait for the process to actually freeze.
|
|
waitpid(pid, NULL, 0);
|
|
|
|
int mem_fd = open_proc_mem_fd();
|
|
if (mem_fd < 0)
|
|
ret = -1;
|
|
|
|
FILE* fmaps = open_proc_maps();
|
|
if (fmaps == NULL)
|
|
ret = -1;
|
|
|
|
if (ret == 0)
|
|
ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment);
|
|
|
|
ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
|
|
|
// Cleanup.
|
|
fflush(stdout);
|
|
close(mem_fd);
|
|
fclose(fmaps);
|
|
return ret;
|
|
}
|