| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2023 Google Inc, Steven Rostedt <rostedt@goodmis.org> |
| * |
| */ |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <errno.h> |
| |
| #include "tracefs.h" |
| #include "trace-local.h" |
| |
| struct timeshift_sample { |
| struct timeshift_sample *next; |
| long long offset; |
| long long scaling; |
| long long timestamp; |
| long long fract; |
| }; |
| |
| struct vcpu_pid { |
| struct vcpu_pid *next; |
| int pid; |
| int cpu; |
| }; |
| |
| static unsigned int num_cpus; |
| |
| static void *vcpu_pids; |
| |
| static struct timeshift_sample *tshifts; |
| static struct timeshift_sample **tshifts_next = &tshifts; |
| |
| static u64 set_value(const char *str, const char *type, u64 def) |
| { |
| if (str && str[0] != '\0' && str[0] != '-' && !isdigit(str[0])) |
| die("Bad %s value", type); |
| |
| if (str && str[0]) |
| return strtoull(str, NULL, 0); |
| |
| return def; |
| } |
| |
| static void add_timeshift(char *shift) |
| { |
| struct timeshift_sample *tshift; |
| char *timestamp_str; |
| char *offset_str; |
| char *scale_str; |
| char *fract_str; |
| char *saveptr; |
| u64 timestamp; |
| u64 offset; |
| u64 scale; |
| u64 fract; |
| |
| offset_str = strparse(shift, ',', &saveptr); |
| scale_str = strparse(NULL, ',', &saveptr); |
| fract_str = strparse(NULL, ',', &saveptr); |
| timestamp_str = strparse(NULL, ',', &saveptr); |
| |
| if (!offset_str) |
| die("Bad timeshift argument"); |
| |
| offset = set_value(offset_str, "offset", 0); |
| scale = set_value(scale_str, "scaling", 1); |
| fract = set_value(fract_str, "fraction", 0); |
| timestamp = set_value(timestamp_str, "timestamp", 0); |
| |
| tshift = calloc(1, sizeof(*tshift)); |
| if (!tshift) |
| die("Could not allocate timeshift"); |
| |
| *tshifts_next = tshift; |
| tshifts_next = &tshift->next; |
| |
| tshift->offset = offset; |
| tshift->scaling = scale; |
| tshift->fract = fract; |
| tshift->timestamp = timestamp; |
| } |
| |
| static void free_timeshifts(void) |
| { |
| struct timeshift_sample *tshift; |
| |
| while (tshifts) { |
| tshift = tshifts; |
| tshifts = tshift->next; |
| free(tshift); |
| } |
| } |
| |
| static void add_vcpu_pid(const char *pid) |
| { |
| struct vcpu_pid *vpid; |
| |
| vpid = calloc(1, sizeof(*vpid)); |
| vpid->pid = atoi(pid); |
| vpid->cpu = -1; |
| vpid->next = vcpu_pids; |
| vcpu_pids = vpid; |
| } |
| |
| static void free_vcpu_pids(void) |
| { |
| struct vcpu_pid *vpid; |
| |
| while (vcpu_pids) { |
| vpid = vcpu_pids; |
| vcpu_pids = vpid->next; |
| free(vpid); |
| } |
| } |
| |
| static inline int test_vcpu_id(struct tep_format_field **vcpu_id_field, |
| struct tep_event *event, struct tep_record *record) |
| { |
| unsigned long long val; |
| struct vcpu_pid *vpid; |
| bool done = true; |
| int pid; |
| int cnt = 0; |
| |
| if (!*vcpu_id_field) { |
| *vcpu_id_field = tep_find_field(event, "vcpu_id"); |
| if (!*vcpu_id_field) |
| die("Could not find vcpu_id field"); |
| } |
| |
| pid = tep_data_pid(event->tep, record); |
| for (vpid = vcpu_pids; vpid; vpid = vpid->next) { |
| if (vpid->cpu < 0) { |
| done = false; |
| } else { |
| cnt++; |
| continue; |
| } |
| if (vpid->pid == pid) |
| break; |
| } |
| |
| if (done || (num_cpus && cnt == num_cpus)) |
| return -1; |
| |
| if (!vpid) |
| return 0; |
| |
| if (tep_read_number_field(*vcpu_id_field, record->data, &val)) |
| die("Could not read data vcpu_id field"); |
| |
| vpid->cpu = (int)val; |
| |
| return 0; |
| } |
| |
| static int entry_callback(struct tracecmd_input *handle, struct tep_event *event, |
| struct tep_record *record, int cpu, void *data) |
| { |
| static struct tep_format_field *vcpu_id_field; |
| |
| return test_vcpu_id(&vcpu_id_field, event, record); |
| } |
| |
| static int exit_callback(struct tracecmd_input *handle, struct tep_event *event, |
| struct tep_record *record, int cpu, void *data) |
| { |
| static struct tep_format_field *vcpu_id_field; |
| |
| return test_vcpu_id(&vcpu_id_field, event, record); |
| } |
| |
| static int cmp_vcpus(const void *A, const void *B) |
| { |
| struct vcpu_pid * const *a = A; |
| struct vcpu_pid * const *b = B; |
| |
| if ((*a)->cpu < (*b)->cpu) |
| return -1; |
| |
| return (*a)->cpu > (*b)->cpu; |
| } |
| |
| static void update_end(char **end, void *data, int size, const char *stop) |
| { |
| char *str = *end; |
| |
| if (str + size > stop) |
| die("Error in calculating buffer size"); |
| |
| memcpy(str, data, size); |
| *end = str + size; |
| } |
| |
| static void add_guest_to_host(struct tracecmd_output *host_ohandle, |
| struct tracecmd_input *guest_ihandle) |
| { |
| unsigned long long guest_id; |
| struct vcpu_pid **vcpu_list; |
| struct vcpu_pid *vpid; |
| char *name = ""; /* TODO, add name for guest */ |
| char *stop; |
| char *buf; |
| char *end; |
| int cpus = 0; |
| int cpu; |
| int size; |
| |
| guest_id = tracecmd_get_traceid(guest_ihandle); |
| |
| for (vpid = vcpu_pids; vpid ; vpid = vpid->next) { |
| if (vpid->cpu < 0) |
| continue; |
| cpus++; |
| } |
| |
| vcpu_list = calloc(cpus, sizeof(*vcpu_list)); |
| if (!vcpu_list) |
| die("Could not allocate vCPU list"); |
| |
| cpus = 0; |
| for (vpid = vcpu_pids; vpid ; vpid = vpid->next) { |
| if (vpid->cpu < 0) |
| continue; |
| vcpu_list[cpus++] = vpid; |
| } |
| |
| qsort(vcpu_list, cpus, sizeof(*vcpu_list), cmp_vcpus); |
| |
| size = strlen(name) + 1; |
| size += sizeof(int) + sizeof(long long); |
| size += cpus * (sizeof(int) * 2); |
| buf = calloc(1, size); |
| if (!buf) |
| die("Failed allocation"); |
| |
| end = buf; |
| stop = buf + size; |
| |
| /* TODO match endianess of existing file */ |
| update_end(&end, name, strlen(name) + 1, stop); |
| update_end(&end, &guest_id, sizeof(guest_id), stop); |
| update_end(&end, &cpus, sizeof(cpus), stop); |
| |
| for (cpu = 0; cpu < cpus; cpu++) { |
| int vcpu = vcpu_list[cpu]->cpu; |
| int pid = vcpu_list[cpu]->pid; |
| update_end(&end, &cpu, sizeof(vcpu), stop); |
| update_end(&end, &pid, sizeof(pid), stop); |
| } |
| |
| if (tracecmd_add_option(host_ohandle, TRACECMD_OPTION_GUEST, size, buf) == NULL) |
| die("Failed to add GUEST option to host"); |
| |
| free(vcpu_list); |
| free(buf); |
| } |
| |
| static void add_timeshift_to_guest(struct tracecmd_output *guest_ohandle, |
| struct tracecmd_input *host_ihandle) |
| { |
| struct timeshift_sample *tshift = tshifts; |
| struct timeshift_sample *last_tshift = NULL; |
| unsigned long long host_id; |
| char *stop; |
| char *end; |
| char *buf; |
| int proto; |
| int size = 0; |
| int cpus; |
| int cpu; |
| |
| host_id = tracecmd_get_traceid(host_ihandle); |
| cpus = num_cpus; |
| proto = 0; /* For now we just have zero */ |
| |
| /* |
| * option size is: |
| * trace id: 8 bytes |
| * protocol flags: 4 bytes |
| * CPU count: 4 bytes |
| * |
| * For each CPU: |
| * sample cnt: 4 bytes |
| * list of times: 8 bytes * sample cnt |
| * list of offsets: 8 bytes * sample cnt |
| * list of scaling: 8 bytes * sample cnt |
| * |
| * For each CPU: |
| * list of fract: 8 bytes * CPU count |
| */ |
| size = 8 + 4 + 4; |
| |
| /* Include fraction bits here */ |
| size += 8 * cpus; |
| |
| /* We only have one sample per CPU (for now) */ |
| size += (4 + 8 * 3) * cpus; |
| |
| buf = calloc(1, size); |
| if (!buf) |
| die("Failed to allocate timeshift buffer"); |
| |
| end = buf; |
| stop = buf + size; |
| update_end(&end, &host_id, sizeof(host_id), stop); |
| update_end(&end, &proto, sizeof(proto), stop); |
| update_end(&end, &cpus, sizeof(cpus), stop); |
| |
| for (cpu = 0; cpu < cpus; cpu++) { |
| struct timeshift_sample *tsample = tshift; |
| unsigned long long sample; |
| int cnt = 1; |
| |
| if (!tsample) |
| tsample = last_tshift; |
| |
| if (!tsample) |
| die("No samples given"); |
| |
| last_tshift = tsample; |
| |
| update_end(&end, &cnt, sizeof(cnt), stop); |
| sample = tsample->timestamp; |
| update_end(&end, &sample, sizeof(sample), stop); |
| |
| sample = tsample->offset; |
| update_end(&end, &sample, sizeof(sample), stop); |
| |
| sample = tsample->scaling; |
| update_end(&end, &sample, sizeof(sample), stop); |
| } |
| |
| tshift = tshifts; |
| last_tshift = NULL; |
| |
| for (cpu = 0; cpu < cpus; cpu++) { |
| struct timeshift_sample *tsample = tshift; |
| unsigned long long sample; |
| |
| if (!tsample) |
| tsample = last_tshift; |
| last_tshift = tsample; |
| |
| sample = tsample->fract; |
| |
| update_end(&end, &sample, sizeof(sample), stop); |
| } |
| |
| if (tracecmd_add_option(guest_ohandle, TRACECMD_OPTION_TIME_SHIFT, size, buf) == NULL) |
| die("Failed to add TIME SHIFT option"); |
| |
| free(buf); |
| } |
| |
| static void add_tsc2nsec_to_guest(struct tracecmd_output *guest_ohandle, |
| struct tracecmd_input *host_ihandle) |
| { |
| unsigned long long offset; |
| int mult; |
| int shift; |
| int ret; |
| char buf[sizeof(int) * 2 + sizeof(long long)]; |
| char *stop; |
| char *end; |
| int size = sizeof(buf); |
| |
| ret = tracecmd_get_tsc2nsec(host_ihandle, &mult, &shift, &offset); |
| if (ret < 0) |
| die("Host does not have tsc2nsec info"); |
| |
| end = buf; |
| stop = buf + size; |
| update_end(&end, &mult, sizeof(mult), stop); |
| update_end(&end, &shift, sizeof(shift), stop); |
| update_end(&end, &offset, sizeof(offset), stop); |
| |
| if (tracecmd_add_option(guest_ohandle, TRACECMD_OPTION_TSC2NSEC, size, buf) == NULL) |
| die("Failed to add TSC2NSEC option"); |
| |
| } |
| |
| static void map_cpus(struct tracecmd_input *handle) |
| { |
| int entry_ret; |
| int exit_ret; |
| |
| entry_ret = tracecmd_follow_event(handle, "kvm", "kvm_entry", entry_callback, NULL); |
| exit_ret = tracecmd_follow_event(handle, "kvm", "kvm_exit", exit_callback, NULL); |
| |
| if (entry_ret < 0 && exit_ret < 0) |
| die("Host needs kvm_exit or kvm_entry events to attach"); |
| |
| tracecmd_iterate_events(handle, NULL, 0, NULL, NULL); |
| } |
| |
| void trace_attach(int argc, char **argv) |
| { |
| struct tracecmd_input *guest_ihandle; |
| struct tracecmd_input *host_ihandle; |
| struct tracecmd_output *guest_ohandle; |
| struct tracecmd_output *host_ohandle; |
| unsigned long long guest_id; |
| char *guest_file; |
| char *host_file; |
| int ret; |
| int fd; |
| |
| for (;;) { |
| int c; |
| |
| c = getopt(argc-1, argv+1, "c:s:h"); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 's': |
| add_timeshift(optarg); |
| break; |
| case 'c': |
| num_cpus = atoi(optarg); |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| /* Account for "attach" */ |
| optind++; |
| |
| if ((argc - optind) < 3) |
| usage(argv); |
| |
| host_file = argv[optind++]; |
| guest_file = argv[optind++]; |
| |
| for (; optind < argc; optind++) |
| add_vcpu_pid(argv[optind]); |
| |
| |
| host_ihandle = tracecmd_open(host_file,TRACECMD_FL_LOAD_NO_PLUGINS ); |
| guest_ihandle = tracecmd_open(guest_file,TRACECMD_FL_LOAD_NO_PLUGINS ); |
| |
| if (!host_ihandle) |
| die("Could not read %s\n", host_file); |
| |
| if (!guest_ihandle) |
| die("Could not read %s\n", guest_file); |
| |
| guest_id = tracecmd_get_traceid(guest_ihandle); |
| if (!guest_id) |
| die("Guest data file does not contain traceid"); |
| |
| map_cpus(host_ihandle); |
| |
| ret = tracecmd_get_guest_cpumap(host_ihandle, guest_id, |
| NULL, NULL, NULL); |
| if (ret == 0) { |
| printf("Guest is already mapped in host (id=0x%llx) .. skipping ...\n", |
| guest_id); |
| } else { |
| |
| fd = open(host_file, O_RDWR); |
| if (fd < 0) |
| die("Could not write %s", host_file); |
| |
| host_ohandle = tracecmd_get_output_handle_fd(fd); |
| if (!host_ohandle) |
| die("Error setting up %s for write", host_file); |
| |
| add_guest_to_host(host_ohandle, guest_ihandle); |
| tracecmd_output_close(host_ohandle); |
| } |
| |
| fd = open(guest_file, O_RDWR); |
| if (fd < 0) |
| die("Could not write %s", guest_file); |
| |
| guest_ohandle = tracecmd_get_output_handle_fd(fd); |
| if (!guest_ohandle) |
| die("Error setting up %s for write", guest_file); |
| |
| add_timeshift_to_guest(guest_ohandle, host_ihandle); |
| add_tsc2nsec_to_guest(guest_ohandle, host_ihandle); |
| |
| tracecmd_output_close(guest_ohandle); |
| |
| tracecmd_close(guest_ihandle); |
| tracecmd_close(host_ihandle); |
| |
| free_timeshifts(); |
| free_vcpu_pids(); |
| |
| return; |
| } |