465 строки
11 KiB
C
465 строки
11 KiB
C
#include "debug.h"
|
|
#include "event.h"
|
|
#include "machine.h"
|
|
#include "map.h"
|
|
#include "strlist.h"
|
|
#include "thread.h"
|
|
#include <stdbool.h>
|
|
|
|
int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
|
|
{
|
|
map_groups__init(&machine->kmaps);
|
|
RB_CLEAR_NODE(&machine->rb_node);
|
|
INIT_LIST_HEAD(&machine->user_dsos);
|
|
INIT_LIST_HEAD(&machine->kernel_dsos);
|
|
|
|
machine->threads = RB_ROOT;
|
|
INIT_LIST_HEAD(&machine->dead_threads);
|
|
machine->last_match = NULL;
|
|
|
|
machine->kmaps.machine = machine;
|
|
machine->pid = pid;
|
|
|
|
machine->root_dir = strdup(root_dir);
|
|
if (machine->root_dir == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (pid != HOST_KERNEL_ID) {
|
|
struct thread *thread = machine__findnew_thread(machine, pid);
|
|
char comm[64];
|
|
|
|
if (thread == NULL)
|
|
return -ENOMEM;
|
|
|
|
snprintf(comm, sizeof(comm), "[guest/%d]", pid);
|
|
thread__set_comm(thread, comm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dsos__delete(struct list_head *dsos)
|
|
{
|
|
struct dso *pos, *n;
|
|
|
|
list_for_each_entry_safe(pos, n, dsos, node) {
|
|
list_del(&pos->node);
|
|
dso__delete(pos);
|
|
}
|
|
}
|
|
|
|
void machine__exit(struct machine *machine)
|
|
{
|
|
map_groups__exit(&machine->kmaps);
|
|
dsos__delete(&machine->user_dsos);
|
|
dsos__delete(&machine->kernel_dsos);
|
|
free(machine->root_dir);
|
|
machine->root_dir = NULL;
|
|
}
|
|
|
|
void machine__delete(struct machine *machine)
|
|
{
|
|
machine__exit(machine);
|
|
free(machine);
|
|
}
|
|
|
|
struct machine *machines__add(struct rb_root *machines, pid_t pid,
|
|
const char *root_dir)
|
|
{
|
|
struct rb_node **p = &machines->rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct machine *pos, *machine = malloc(sizeof(*machine));
|
|
|
|
if (machine == NULL)
|
|
return NULL;
|
|
|
|
if (machine__init(machine, root_dir, pid) != 0) {
|
|
free(machine);
|
|
return NULL;
|
|
}
|
|
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
pos = rb_entry(parent, struct machine, rb_node);
|
|
if (pid < pos->pid)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
}
|
|
|
|
rb_link_node(&machine->rb_node, parent, p);
|
|
rb_insert_color(&machine->rb_node, machines);
|
|
|
|
return machine;
|
|
}
|
|
|
|
struct machine *machines__find(struct rb_root *machines, pid_t pid)
|
|
{
|
|
struct rb_node **p = &machines->rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct machine *machine;
|
|
struct machine *default_machine = NULL;
|
|
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
machine = rb_entry(parent, struct machine, rb_node);
|
|
if (pid < machine->pid)
|
|
p = &(*p)->rb_left;
|
|
else if (pid > machine->pid)
|
|
p = &(*p)->rb_right;
|
|
else
|
|
return machine;
|
|
if (!machine->pid)
|
|
default_machine = machine;
|
|
}
|
|
|
|
return default_machine;
|
|
}
|
|
|
|
struct machine *machines__findnew(struct rb_root *machines, pid_t pid)
|
|
{
|
|
char path[PATH_MAX];
|
|
const char *root_dir = "";
|
|
struct machine *machine = machines__find(machines, pid);
|
|
|
|
if (machine && (machine->pid == pid))
|
|
goto out;
|
|
|
|
if ((pid != HOST_KERNEL_ID) &&
|
|
(pid != DEFAULT_GUEST_KERNEL_ID) &&
|
|
(symbol_conf.guestmount)) {
|
|
sprintf(path, "%s/%d", symbol_conf.guestmount, pid);
|
|
if (access(path, R_OK)) {
|
|
static struct strlist *seen;
|
|
|
|
if (!seen)
|
|
seen = strlist__new(true, NULL);
|
|
|
|
if (!strlist__has_entry(seen, path)) {
|
|
pr_err("Can't access file %s\n", path);
|
|
strlist__add(seen, path);
|
|
}
|
|
machine = NULL;
|
|
goto out;
|
|
}
|
|
root_dir = path;
|
|
}
|
|
|
|
machine = machines__add(machines, pid, root_dir);
|
|
out:
|
|
return machine;
|
|
}
|
|
|
|
void machines__process(struct rb_root *machines,
|
|
machine__process_t process, void *data)
|
|
{
|
|
struct rb_node *nd;
|
|
|
|
for (nd = rb_first(machines); nd; nd = rb_next(nd)) {
|
|
struct machine *pos = rb_entry(nd, struct machine, rb_node);
|
|
process(pos, data);
|
|
}
|
|
}
|
|
|
|
char *machine__mmap_name(struct machine *machine, char *bf, size_t size)
|
|
{
|
|
if (machine__is_host(machine))
|
|
snprintf(bf, size, "[%s]", "kernel.kallsyms");
|
|
else if (machine__is_default_guest(machine))
|
|
snprintf(bf, size, "[%s]", "guest.kernel.kallsyms");
|
|
else {
|
|
snprintf(bf, size, "[%s.%d]", "guest.kernel.kallsyms",
|
|
machine->pid);
|
|
}
|
|
|
|
return bf;
|
|
}
|
|
|
|
void machines__set_id_hdr_size(struct rb_root *machines, u16 id_hdr_size)
|
|
{
|
|
struct rb_node *node;
|
|
struct machine *machine;
|
|
|
|
for (node = rb_first(machines); node; node = rb_next(node)) {
|
|
machine = rb_entry(node, struct machine, rb_node);
|
|
machine->id_hdr_size = id_hdr_size;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static struct thread *__machine__findnew_thread(struct machine *machine, pid_t pid,
|
|
bool create)
|
|
{
|
|
struct rb_node **p = &machine->threads.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct thread *th;
|
|
|
|
/*
|
|
* Font-end cache - PID lookups come in blocks,
|
|
* so most of the time we dont have to look up
|
|
* the full rbtree:
|
|
*/
|
|
if (machine->last_match && machine->last_match->pid == pid)
|
|
return machine->last_match;
|
|
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
th = rb_entry(parent, struct thread, rb_node);
|
|
|
|
if (th->pid == pid) {
|
|
machine->last_match = th;
|
|
return th;
|
|
}
|
|
|
|
if (pid < th->pid)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
}
|
|
|
|
if (!create)
|
|
return NULL;
|
|
|
|
th = thread__new(pid);
|
|
if (th != NULL) {
|
|
rb_link_node(&th->rb_node, parent, p);
|
|
rb_insert_color(&th->rb_node, &machine->threads);
|
|
machine->last_match = th;
|
|
}
|
|
|
|
return th;
|
|
}
|
|
|
|
struct thread *machine__findnew_thread(struct machine *machine, pid_t pid)
|
|
{
|
|
return __machine__findnew_thread(machine, pid, true);
|
|
}
|
|
|
|
struct thread *machine__find_thread(struct machine *machine, pid_t pid)
|
|
{
|
|
return __machine__findnew_thread(machine, pid, false);
|
|
}
|
|
|
|
int machine__process_comm_event(struct machine *machine, union perf_event *event)
|
|
{
|
|
struct thread *thread = machine__findnew_thread(machine, event->comm.tid);
|
|
|
|
if (dump_trace)
|
|
perf_event__fprintf_comm(event, stdout);
|
|
|
|
if (thread == NULL || thread__set_comm(thread, event->comm.comm)) {
|
|
dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int machine__process_lost_event(struct machine *machine __maybe_unused,
|
|
union perf_event *event)
|
|
{
|
|
dump_printf(": id:%" PRIu64 ": lost:%" PRIu64 "\n",
|
|
event->lost.id, event->lost.lost);
|
|
return 0;
|
|
}
|
|
|
|
static void machine__set_kernel_mmap_len(struct machine *machine,
|
|
union perf_event *event)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAP__NR_TYPES; i++) {
|
|
machine->vmlinux_maps[i]->start = event->mmap.start;
|
|
machine->vmlinux_maps[i]->end = (event->mmap.start +
|
|
event->mmap.len);
|
|
/*
|
|
* Be a bit paranoid here, some perf.data file came with
|
|
* a zero sized synthesized MMAP event for the kernel.
|
|
*/
|
|
if (machine->vmlinux_maps[i]->end == 0)
|
|
machine->vmlinux_maps[i]->end = ~0ULL;
|
|
}
|
|
}
|
|
|
|
static int machine__process_kernel_mmap_event(struct machine *machine,
|
|
union perf_event *event)
|
|
{
|
|
struct map *map;
|
|
char kmmap_prefix[PATH_MAX];
|
|
enum dso_kernel_type kernel_type;
|
|
bool is_kernel_mmap;
|
|
|
|
machine__mmap_name(machine, kmmap_prefix, sizeof(kmmap_prefix));
|
|
if (machine__is_host(machine))
|
|
kernel_type = DSO_TYPE_KERNEL;
|
|
else
|
|
kernel_type = DSO_TYPE_GUEST_KERNEL;
|
|
|
|
is_kernel_mmap = memcmp(event->mmap.filename,
|
|
kmmap_prefix,
|
|
strlen(kmmap_prefix) - 1) == 0;
|
|
if (event->mmap.filename[0] == '/' ||
|
|
(!is_kernel_mmap && event->mmap.filename[0] == '[')) {
|
|
|
|
char short_module_name[1024];
|
|
char *name, *dot;
|
|
|
|
if (event->mmap.filename[0] == '/') {
|
|
name = strrchr(event->mmap.filename, '/');
|
|
if (name == NULL)
|
|
goto out_problem;
|
|
|
|
++name; /* skip / */
|
|
dot = strrchr(name, '.');
|
|
if (dot == NULL)
|
|
goto out_problem;
|
|
snprintf(short_module_name, sizeof(short_module_name),
|
|
"[%.*s]", (int)(dot - name), name);
|
|
strxfrchar(short_module_name, '-', '_');
|
|
} else
|
|
strcpy(short_module_name, event->mmap.filename);
|
|
|
|
map = machine__new_module(machine, event->mmap.start,
|
|
event->mmap.filename);
|
|
if (map == NULL)
|
|
goto out_problem;
|
|
|
|
name = strdup(short_module_name);
|
|
if (name == NULL)
|
|
goto out_problem;
|
|
|
|
map->dso->short_name = name;
|
|
map->dso->sname_alloc = 1;
|
|
map->end = map->start + event->mmap.len;
|
|
} else if (is_kernel_mmap) {
|
|
const char *symbol_name = (event->mmap.filename +
|
|
strlen(kmmap_prefix));
|
|
/*
|
|
* Should be there already, from the build-id table in
|
|
* the header.
|
|
*/
|
|
struct dso *kernel = __dsos__findnew(&machine->kernel_dsos,
|
|
kmmap_prefix);
|
|
if (kernel == NULL)
|
|
goto out_problem;
|
|
|
|
kernel->kernel = kernel_type;
|
|
if (__machine__create_kernel_maps(machine, kernel) < 0)
|
|
goto out_problem;
|
|
|
|
machine__set_kernel_mmap_len(machine, event);
|
|
|
|
/*
|
|
* Avoid using a zero address (kptr_restrict) for the ref reloc
|
|
* symbol. Effectively having zero here means that at record
|
|
* time /proc/sys/kernel/kptr_restrict was non zero.
|
|
*/
|
|
if (event->mmap.pgoff != 0) {
|
|
maps__set_kallsyms_ref_reloc_sym(machine->vmlinux_maps,
|
|
symbol_name,
|
|
event->mmap.pgoff);
|
|
}
|
|
|
|
if (machine__is_default_guest(machine)) {
|
|
/*
|
|
* preload dso of guest kernel and modules
|
|
*/
|
|
dso__load(kernel, machine->vmlinux_maps[MAP__FUNCTION],
|
|
NULL);
|
|
}
|
|
}
|
|
return 0;
|
|
out_problem:
|
|
return -1;
|
|
}
|
|
|
|
int machine__process_mmap_event(struct machine *machine, union perf_event *event)
|
|
{
|
|
u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
|
|
struct thread *thread;
|
|
struct map *map;
|
|
int ret = 0;
|
|
|
|
if (dump_trace)
|
|
perf_event__fprintf_mmap(event, stdout);
|
|
|
|
if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
|
|
cpumode == PERF_RECORD_MISC_KERNEL) {
|
|
ret = machine__process_kernel_mmap_event(machine, event);
|
|
if (ret < 0)
|
|
goto out_problem;
|
|
return 0;
|
|
}
|
|
|
|
thread = machine__findnew_thread(machine, event->mmap.pid);
|
|
if (thread == NULL)
|
|
goto out_problem;
|
|
map = map__new(&machine->user_dsos, event->mmap.start,
|
|
event->mmap.len, event->mmap.pgoff,
|
|
event->mmap.pid, event->mmap.filename,
|
|
MAP__FUNCTION);
|
|
if (map == NULL)
|
|
goto out_problem;
|
|
|
|
thread__insert_map(thread, map);
|
|
return 0;
|
|
|
|
out_problem:
|
|
dump_printf("problem processing PERF_RECORD_MMAP, skipping event.\n");
|
|
return 0;
|
|
}
|
|
|
|
int machine__process_fork_event(struct machine *machine, union perf_event *event)
|
|
{
|
|
struct thread *thread = machine__findnew_thread(machine, event->fork.tid);
|
|
struct thread *parent = machine__findnew_thread(machine, event->fork.ptid);
|
|
|
|
if (dump_trace)
|
|
perf_event__fprintf_task(event, stdout);
|
|
|
|
if (thread == NULL || parent == NULL ||
|
|
thread__fork(thread, parent) < 0) {
|
|
dump_printf("problem processing PERF_RECORD_FORK, skipping event.\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int machine__process_exit_event(struct machine *machine, union perf_event *event)
|
|
{
|
|
struct thread *thread = machine__find_thread(machine, event->fork.tid);
|
|
|
|
if (dump_trace)
|
|
perf_event__fprintf_task(event, stdout);
|
|
|
|
if (thread != NULL)
|
|
machine__remove_thread(machine, thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int machine__process_event(struct machine *machine, union perf_event *event)
|
|
{
|
|
int ret;
|
|
|
|
switch (event->header.type) {
|
|
case PERF_RECORD_COMM:
|
|
ret = machine__process_comm_event(machine, event); break;
|
|
case PERF_RECORD_MMAP:
|
|
ret = machine__process_mmap_event(machine, event); break;
|
|
case PERF_RECORD_FORK:
|
|
ret = machine__process_fork_event(machine, event); break;
|
|
case PERF_RECORD_EXIT:
|
|
ret = machine__process_exit_event(machine, event); break;
|
|
case PERF_RECORD_LOST:
|
|
ret = machine__process_lost_event(machine, event); break;
|
|
default:
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|