| #include <stdlib.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <sys/socket.h> |
| #include <linux/vm_sockets.h> |
| |
| #include <tracefs.h> |
| |
| static int open_vsock(unsigned int cid, unsigned int port) |
| { |
| struct sockaddr_vm addr = { |
| .svm_family = AF_VSOCK, |
| .svm_cid = cid, |
| .svm_port = port, |
| }; |
| int sd; |
| |
| sd = socket(AF_VSOCK, SOCK_STREAM, 0); |
| if (sd < 0) |
| return -1; |
| |
| if (connect(sd, (struct sockaddr *)&addr, sizeof(addr))) { |
| close(sd); |
| return -1; |
| } |
| |
| return sd; |
| } |
| |
| struct pids { |
| struct pids *next; |
| int pid; |
| }; |
| |
| struct trace_info { |
| struct tracefs_instance *instance; |
| struct tep_handle *tep; |
| struct tep_event *wake_up; |
| struct tep_event *kvm_exit; |
| struct tep_format_field *wake_pid; |
| struct pids *pids; |
| int pid; |
| }; |
| |
| static void tear_down_trace(struct trace_info *info) |
| { |
| tracefs_event_disable(info->instance, NULL, NULL); |
| tep_free(info->tep); |
| info->tep = NULL; |
| } |
| |
| static int add_pid(struct pids **pids, int pid) |
| { |
| struct pids *new_pid; |
| |
| new_pid = malloc(sizeof(*new_pid)); |
| if (!new_pid) |
| return -1; |
| |
| new_pid->pid = pid; |
| new_pid->next = *pids; |
| *pids = new_pid; |
| return 0; |
| } |
| |
| static bool match_pid(struct pids *pids, int pid) |
| { |
| while (pids) { |
| if (pids->pid == pid) |
| return true; |
| pids = pids->next; |
| } |
| return false; |
| } |
| |
| static int waking_callback(struct tep_event *event, struct tep_record *record, |
| int cpu, void *data) |
| { |
| struct trace_info *info = data; |
| unsigned long long val; |
| int flags; |
| int pid; |
| int ret; |
| |
| pid = tep_data_pid(event->tep, record); |
| if (!match_pid(info->pids, pid)) |
| return 0; |
| |
| /* Ignore wakeups in interrupts */ |
| flags = tep_data_flags(event->tep, record); |
| if (flags & (TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ)) |
| return 0; |
| |
| if (!info->wake_pid) { |
| info->wake_pid = tep_find_field(event, "pid"); |
| |
| if (!info->wake_pid) |
| return -1; |
| } |
| |
| ret = tep_read_number_field(info->wake_pid, record->data, &val); |
| if (ret < 0) |
| return -1; |
| |
| return add_pid(&info->pids, (int)val); |
| } |
| |
| static int exit_callback(struct tep_event *event, struct tep_record *record, |
| int cpu, void *data) |
| { |
| struct trace_info *info = data; |
| int pid; |
| |
| pid = tep_data_pid(event->tep, record); |
| if (!match_pid(info->pids, pid)) |
| return 0; |
| |
| info->pid = pid; |
| |
| /* Found the pid we are looking for, stop the trace */ |
| return -1; |
| } |
| |
| static int setup_trace(struct trace_info *info) |
| { |
| const char *systems[] = { "sched", "kvm", NULL}; |
| int ret; |
| |
| info->pids = NULL; |
| |
| tracefs_trace_off(info->instance); |
| info->tep = tracefs_local_events_system(NULL, systems); |
| if (!info->tep) |
| return -1; |
| |
| /* |
| * Follow the wake ups, starting with this pid, to find |
| * the one that exits to the guest. That will be the thread |
| * of the vCPU of the guest. |
| */ |
| ret = tracefs_follow_event(info->tep, info->instance, |
| "sched", "sched_waking", |
| waking_callback, info); |
| if (ret < 0) |
| goto fail; |
| |
| ret = tracefs_follow_event(info->tep, info->instance, |
| "kvm", "kvm_exit", |
| exit_callback, info); |
| if (ret < 0) |
| goto fail; |
| |
| ret = tracefs_event_enable(info->instance, "sched", "sched_waking"); |
| if (ret < 0) |
| goto fail; |
| |
| ret = tracefs_event_enable(info->instance, "kvm", "kvm_exit"); |
| if (ret < 0) |
| goto fail; |
| |
| return 0; |
| fail: |
| tear_down_trace(info); |
| return -1; |
| } |
| |
| |
| static void free_pids(struct pids *pids) |
| { |
| struct pids *next; |
| |
| while (pids) { |
| next = pids; |
| pids = pids->next; |
| free(next); |
| } |
| } |
| |
| static int find_thread_leader(int pid) |
| { |
| FILE *fp; |
| char *path; |
| char *save; |
| char *buf = NULL; |
| 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); |
| |
| return tgid > 0 ? tgid : -1; |
| } |
| |
| int tracefs_instance_find_cid_pid(struct tracefs_instance *instance, int cid) |
| { |
| struct trace_info info = {}; |
| int this_pid = getpid(); |
| int ret; |
| int fd; |
| |
| info.instance = instance; |
| |
| if (setup_trace(&info) < 0) |
| return -1; |
| |
| ret = add_pid(&info.pids, this_pid); |
| if (ret < 0) |
| goto out; |
| |
| tracefs_instance_file_clear(info.instance, "trace"); |
| tracefs_trace_on(info.instance); |
| fd = open_vsock(cid, -1); |
| tracefs_trace_off(info.instance); |
| if (fd >= 0) |
| close(fd); |
| info.pid = -1; |
| ret = tracefs_iterate_raw_events(info.tep, info.instance, |
| NULL, 0, NULL, &info); |
| if (info.pid <= 0) |
| ret = -1; |
| if (ret == 0) |
| ret = find_thread_leader(info.pid); |
| |
| out: |
| free_pids(info.pids); |
| info.pids = NULL; |
| tear_down_trace(&info); |
| |
| return ret; |
| } |
| |
| int tracefs_find_cid_pid(int cid) |
| { |
| struct tracefs_instance *instance; |
| char *name; |
| int ret; |
| |
| ret = asprintf(&name, "_tracefs_vsock_find-%d\n", getpid()); |
| if (ret < 0) |
| return ret; |
| |
| instance = tracefs_instance_create(name); |
| free(name); |
| if (!instance) |
| return -1; |
| |
| ret = tracefs_instance_find_cid_pid(instance, cid); |
| |
| tracefs_instance_destroy(instance); |
| tracefs_instance_free(instance); |
| |
| return ret; |
| } |