// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt * Copyright (C) 2020, VMware, Tzvetomir Stoyanov * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include #include #include #include #include #include #include #include "trace-local.h" #include "trace-msg.h" static struct trace_guest *guests; static size_t guests_len; static struct trace_guest *get_guest_by_cid(unsigned int guest_cid) { int i; if (!guests) return NULL; for (i = 0; i < guests_len; i++) if (guest_cid == guests[i].cid) return guests + i; return NULL; } static struct trace_guest *get_guest_by_name(const char *name) { int i; if (!guests) return NULL; for (i = 0; i < guests_len; i++) if (strcmp(name, guests[i].name) == 0) return guests + i; return NULL; } bool trace_have_guests_pid(void) { for (int i = 0; i < guests_len; i++) { if (guests[i].pid < 0) return false; } return true; } static struct trace_guest *add_guest(unsigned int cid, const char *name) { guests = realloc(guests, (guests_len + 1) * sizeof(*guests)); if (!guests) die("allocating new guest"); memset(&guests[guests_len], 0, sizeof(struct trace_guest)); guests[guests_len].name = strdup(name); if (!guests[guests_len].name) die("allocating guest name"); guests[guests_len].cid = cid; guests[guests_len].pid = -1; guests_len++; return &guests[guests_len - 1]; } static struct tracefs_instance *start_trace_connect(void) { struct tracefs_instance *open_instance; open_instance = tracefs_instance_create("vsock_find_pid"); if (!open_instance) return NULL; tracefs_event_enable(open_instance, "sched", "sched_waking"); tracefs_event_enable(open_instance, "kvm", "kvm_exit"); tracefs_trace_on(open_instance); return open_instance; } struct pids { struct pids *next; int pid; }; struct trace_fields { struct tep_event *sched_waking; struct tep_event *kvm_exit; struct tep_format_field *common_pid; struct tep_format_field *sched_next; struct pids *pids; int found_pid; }; static void free_pids(struct pids *pids) { struct pids *next; while (pids) { next = pids; pids = pids->next; free(next); } } static void add_pid(struct pids **pids, int pid) { struct pids *new_pid; new_pid = malloc(sizeof(*new_pid)); if (!new_pid) return; new_pid->pid = pid; new_pid->next = *pids; *pids = new_pid; } static bool match_pid(struct pids *pids, int pid) { while (pids) { if (pids->pid == pid) return true; pids = pids->next; } return false; } static int callback(struct tep_event *event, struct tep_record *record, int cpu, void *data) { struct trace_fields *fields = data; struct tep_handle *tep = event->tep; unsigned long long val; int flags; int type; int pid; int ret; ret = tep_read_number_field(fields->common_pid, record->data, &val); if (ret < 0) return 0; flags = tep_data_flags(tep, record); /* Ignore events in interrupts */ if (flags & (TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ)) return 0; /* * First make sure that this event comes from a PID from * this task (or a task woken by this task) */ pid = val; if (!match_pid(fields->pids, pid)) return 0; type = tep_data_type(tep, record); /* * If this event is a kvm_exit, we have our PID * and we can stop processing. */ if (type == fields->kvm_exit->id) { fields->found_pid = pid; return -1; } if (type != fields->sched_waking->id) return 0; ret = tep_read_number_field(fields->sched_next, record->data, &val); if (ret < 0) return 0; /* This is a task woken by our task or a chain of wake ups */ add_pid(&fields->pids, (int)val); return 0; } static int find_tgid(int pid) { FILE *fp; char *path; char *buf = NULL; char *save; size_t l = 0; int tgid = -1; if (asprintf(&path, "/proc/%d/status", pid) < 0) return -1; fp = fopen(path, "r"); free(path); if (!fp) return -1; while (getline(&buf, &l, fp) > 0) { char *tok; if (strncmp(buf, "Tgid:", 5) != 0) continue; tok = strtok_r(buf, ":", &save); if (!tok) continue; tok = strtok_r(NULL, ":", &save); if (!tok) continue; while (isspace(*tok)) tok++; tgid = strtol(tok, NULL, 0); break; } free(buf); fclose(fp); return tgid; } static int stop_trace_connect(struct tracefs_instance *open_instance) { const char *systems[] = { "kvm", "sched", NULL}; struct tep_handle *tep; struct trace_fields trace_fields; int tgid = -1; if (!open_instance) return -1; /* The connection is finished, stop tracing, we have what we want */ tracefs_trace_off(open_instance); tracefs_event_disable(open_instance, NULL, NULL); tep = tracefs_local_events_system(NULL, systems); trace_fields.sched_waking = tep_find_event_by_name(tep, "sched", "sched_waking"); if (!trace_fields.sched_waking) goto out; trace_fields.kvm_exit = tep_find_event_by_name(tep, "kvm", "kvm_exit"); if (!trace_fields.kvm_exit) goto out; trace_fields.common_pid = tep_find_common_field(trace_fields.sched_waking, "common_pid"); if (!trace_fields.common_pid) goto out; trace_fields.sched_next = tep_find_any_field(trace_fields.sched_waking, "pid"); if (!trace_fields.sched_next) goto out; trace_fields.found_pid = -1; trace_fields.pids = NULL; add_pid(&trace_fields.pids, getpid()); tracefs_iterate_raw_events(tep, open_instance, NULL, 0, callback, &trace_fields); free_pids(trace_fields.pids); out: tracefs_instance_destroy(open_instance); tracefs_instance_free(open_instance); if (trace_fields.found_pid > 0) tgid = find_tgid(trace_fields.found_pid); return tgid; } /* * In order to find the guest that is associated to the given cid, * trace the sched_waking and kvm_exit events, connect to the cid * (doesn't matter what port, use -1 to not connect to anything) * and find what task gets woken up from this code and calls kvm_exit, * then that is the task that is running the guest. * Then look at the /proc//status file to find the task group * id (Tgid), and this is the PID of the task running all the threads. */ static void find_pid_by_cid(struct trace_guest *guest) { struct tracefs_instance *instance; int fd; instance = start_trace_connect(); fd = trace_vsock_open(guest->cid, -1); guest->pid = stop_trace_connect(instance); /* Just in case! */ if (fd >= 0) close(fd); } struct trace_guest *trace_get_guest(unsigned int cid, const char *name) { struct trace_guest *guest = NULL; if (name) { guest = get_guest_by_name(name); if (guest) return guest; } if (cid > 0) { guest = get_guest_by_cid(cid); if (!guest && name) { guest = add_guest(cid, name); if (guest) find_pid_by_cid(guest); } } return guest; } #define VM_CID_CMD "virsh dumpxml" #define VM_CID_LINE "= guests[i].cpu_max) continue; if (guest_cid == guests[i].cid) return guests[i].cpu_pid[guest_vcpu]; } return -1; }