1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2023 Google Inc, Steven Rostedt <rostedt@goodmis.org>
4 *
5 */
6 #include <stdlib.h>
7 #include <ctype.h>
8 #include <getopt.h>
9 #include <errno.h>
10
11 #include "tracefs.h"
12 #include "trace-local.h"
13
14 struct timeshift_sample {
15 struct timeshift_sample *next;
16 long long offset;
17 long long scaling;
18 long long timestamp;
19 long long fract;
20 };
21
22 struct vcpu_pid {
23 struct vcpu_pid *next;
24 int pid;
25 int cpu;
26 };
27
28 static unsigned int num_cpus;
29
30 static void *vcpu_pids;
31
32 static struct timeshift_sample *tshifts;
33 static struct timeshift_sample **tshifts_next = &tshifts;
34
set_value(const char * str,const char * type,u64 def)35 static u64 set_value(const char *str, const char *type, u64 def)
36 {
37 if (str && str[0] != '\0' && str[0] != '-' && !isdigit(str[0]))
38 die("Bad %s value", type);
39
40 if (str && str[0])
41 return strtoull(str, NULL, 0);
42
43 return def;
44 }
45
add_timeshift(char * shift)46 static void add_timeshift(char *shift)
47 {
48 struct timeshift_sample *tshift;
49 char *timestamp_str;
50 char *offset_str;
51 char *scale_str;
52 char *fract_str;
53 char *saveptr;
54 u64 timestamp;
55 u64 offset;
56 u64 scale;
57 u64 fract;
58
59 offset_str = strparse(shift, ',', &saveptr);
60 scale_str = strparse(NULL, ',', &saveptr);
61 fract_str = strparse(NULL, ',', &saveptr);
62 timestamp_str = strparse(NULL, ',', &saveptr);
63
64 if (!offset_str)
65 die("Bad timeshift argument");
66
67 offset = set_value(offset_str, "offset", 0);
68 scale = set_value(scale_str, "scaling", 1);
69 fract = set_value(fract_str, "fraction", 0);
70 timestamp = set_value(timestamp_str, "timestamp", 0);
71
72 tshift = calloc(1, sizeof(*tshift));
73 if (!tshift)
74 die("Could not allocate timeshift");
75
76 *tshifts_next = tshift;
77 tshifts_next = &tshift->next;
78
79 tshift->offset = offset;
80 tshift->scaling = scale;
81 tshift->fract = fract;
82 tshift->timestamp = timestamp;
83 }
84
free_timeshifts(void)85 static void free_timeshifts(void)
86 {
87 struct timeshift_sample *tshift;
88
89 while (tshifts) {
90 tshift = tshifts;
91 tshifts = tshift->next;
92 free(tshift);
93 }
94 }
95
add_vcpu_pid(const char * pid)96 static void add_vcpu_pid(const char *pid)
97 {
98 struct vcpu_pid *vpid;
99
100 vpid = calloc(1, sizeof(*vpid));
101 vpid->pid = atoi(pid);
102 vpid->cpu = -1;
103 vpid->next = vcpu_pids;
104 vcpu_pids = vpid;
105 }
106
free_vcpu_pids(void)107 static void free_vcpu_pids(void)
108 {
109 struct vcpu_pid *vpid;
110
111 while (vcpu_pids) {
112 vpid = vcpu_pids;
113 vcpu_pids = vpid->next;
114 free(vpid);
115 }
116 }
117
test_vcpu_id(struct tep_format_field ** vcpu_id_field,struct tep_event * event,struct tep_record * record)118 static inline int test_vcpu_id(struct tep_format_field **vcpu_id_field,
119 struct tep_event *event, struct tep_record *record)
120 {
121 unsigned long long val;
122 struct vcpu_pid *vpid;
123 bool done = true;
124 int pid;
125 int cnt = 0;
126
127 if (!*vcpu_id_field) {
128 *vcpu_id_field = tep_find_field(event, "vcpu_id");
129 if (!*vcpu_id_field)
130 die("Could not find vcpu_id field");
131 }
132
133 pid = tep_data_pid(event->tep, record);
134 for (vpid = vcpu_pids; vpid; vpid = vpid->next) {
135 if (vpid->cpu < 0) {
136 done = false;
137 } else {
138 cnt++;
139 continue;
140 }
141 if (vpid->pid == pid)
142 break;
143 }
144
145 if (done || (num_cpus && cnt == num_cpus))
146 return -1;
147
148 if (!vpid)
149 return 0;
150
151 if (tep_read_number_field(*vcpu_id_field, record->data, &val))
152 die("Could not read data vcpu_id field");
153
154 vpid->cpu = (int)val;
155
156 return 0;
157 }
158
entry_callback(struct tracecmd_input * handle,struct tep_event * event,struct tep_record * record,int cpu,void * data)159 static int entry_callback(struct tracecmd_input *handle, struct tep_event *event,
160 struct tep_record *record, int cpu, void *data)
161 {
162 static struct tep_format_field *vcpu_id_field;
163
164 return test_vcpu_id(&vcpu_id_field, event, record);
165 }
166
exit_callback(struct tracecmd_input * handle,struct tep_event * event,struct tep_record * record,int cpu,void * data)167 static int exit_callback(struct tracecmd_input *handle, struct tep_event *event,
168 struct tep_record *record, int cpu, void *data)
169 {
170 static struct tep_format_field *vcpu_id_field;
171
172 return test_vcpu_id(&vcpu_id_field, event, record);
173 }
174
cmp_vcpus(const void * A,const void * B)175 static int cmp_vcpus(const void *A, const void *B)
176 {
177 struct vcpu_pid * const *a = A;
178 struct vcpu_pid * const *b = B;
179
180 if ((*a)->cpu < (*b)->cpu)
181 return -1;
182
183 return (*a)->cpu > (*b)->cpu;
184 }
185
update_end(char ** end,void * data,int size,const char * stop)186 static void update_end(char **end, void *data, int size, const char *stop)
187 {
188 char *str = *end;
189
190 if (str + size > stop)
191 die("Error in calculating buffer size");
192
193 memcpy(str, data, size);
194 *end = str + size;
195 }
196
add_guest_to_host(struct tracecmd_output * host_ohandle,struct tracecmd_input * guest_ihandle)197 static void add_guest_to_host(struct tracecmd_output *host_ohandle,
198 struct tracecmd_input *guest_ihandle)
199 {
200 unsigned long long guest_id;
201 struct vcpu_pid **vcpu_list;
202 struct vcpu_pid *vpid;
203 char *name = ""; /* TODO, add name for guest */
204 char *stop;
205 char *buf;
206 char *end;
207 int cpus = 0;
208 int cpu;
209 int size;
210
211 guest_id = tracecmd_get_traceid(guest_ihandle);
212
213 for (vpid = vcpu_pids; vpid ; vpid = vpid->next) {
214 if (vpid->cpu < 0)
215 continue;
216 cpus++;
217 }
218
219 vcpu_list = calloc(cpus, sizeof(*vcpu_list));
220 if (!vcpu_list)
221 die("Could not allocate vCPU list");
222
223 cpus = 0;
224 for (vpid = vcpu_pids; vpid ; vpid = vpid->next) {
225 if (vpid->cpu < 0)
226 continue;
227 vcpu_list[cpus++] = vpid;
228 }
229
230 qsort(vcpu_list, cpus, sizeof(*vcpu_list), cmp_vcpus);
231
232 size = strlen(name) + 1;
233 size += sizeof(int) + sizeof(long long);
234 size += cpus * (sizeof(int) * 2);
235 buf = calloc(1, size);
236 if (!buf)
237 die("Failed allocation");
238
239 end = buf;
240 stop = buf + size;
241
242 /* TODO match endianess of existing file */
243 update_end(&end, name, strlen(name) + 1, stop);
244 update_end(&end, &guest_id, sizeof(guest_id), stop);
245 update_end(&end, &cpus, sizeof(cpus), stop);
246
247 for (cpu = 0; cpu < cpus; cpu++) {
248 int vcpu = vcpu_list[cpu]->cpu;
249 int pid = vcpu_list[cpu]->pid;
250 update_end(&end, &cpu, sizeof(vcpu), stop);
251 update_end(&end, &pid, sizeof(pid), stop);
252 }
253
254 if (tracecmd_add_option(host_ohandle, TRACECMD_OPTION_GUEST, size, buf) == NULL)
255 die("Failed to add GUEST option to host");
256
257 free(vcpu_list);
258 free(buf);
259 }
260
add_timeshift_to_guest(struct tracecmd_output * guest_ohandle,struct tracecmd_input * host_ihandle)261 static void add_timeshift_to_guest(struct tracecmd_output *guest_ohandle,
262 struct tracecmd_input *host_ihandle)
263 {
264 struct timeshift_sample *tshift = tshifts;
265 struct timeshift_sample *last_tshift = NULL;
266 unsigned long long host_id;
267 char *stop;
268 char *end;
269 char *buf;
270 int proto;
271 int size = 0;
272 int cpus;
273 int cpu;
274
275 host_id = tracecmd_get_traceid(host_ihandle);
276 cpus = num_cpus;
277 proto = 0; /* For now we just have zero */
278
279 /*
280 * option size is:
281 * trace id: 8 bytes
282 * protocol flags: 4 bytes
283 * CPU count: 4 bytes
284 *
285 * For each CPU:
286 * sample cnt: 4 bytes
287 * list of times: 8 bytes * sample cnt
288 * list of offsets: 8 bytes * sample cnt
289 * list of scaling: 8 bytes * sample cnt
290 *
291 * For each CPU:
292 * list of fract: 8 bytes * CPU count
293 */
294 size = 8 + 4 + 4;
295
296 /* Include fraction bits here */
297 size += 8 * cpus;
298
299 /* We only have one sample per CPU (for now) */
300 size += (4 + 8 * 3) * cpus;
301
302 buf = calloc(1, size);
303 if (!buf)
304 die("Failed to allocate timeshift buffer");
305
306 end = buf;
307 stop = buf + size;
308 update_end(&end, &host_id, sizeof(host_id), stop);
309 update_end(&end, &proto, sizeof(proto), stop);
310 update_end(&end, &cpus, sizeof(cpus), stop);
311
312 for (cpu = 0; cpu < cpus; cpu++) {
313 struct timeshift_sample *tsample = tshift;
314 unsigned long long sample;
315 int cnt = 1;
316
317 if (!tsample)
318 tsample = last_tshift;
319
320 if (!tsample)
321 die("No samples given");
322
323 last_tshift = tsample;
324
325 update_end(&end, &cnt, sizeof(cnt), stop);
326 sample = tsample->timestamp;
327 update_end(&end, &sample, sizeof(sample), stop);
328
329 sample = tsample->offset;
330 update_end(&end, &sample, sizeof(sample), stop);
331
332 sample = tsample->scaling;
333 update_end(&end, &sample, sizeof(sample), stop);
334 }
335
336 tshift = tshifts;
337 last_tshift = NULL;
338
339 for (cpu = 0; cpu < cpus; cpu++) {
340 struct timeshift_sample *tsample = tshift;
341 unsigned long long sample;
342
343 if (!tsample)
344 tsample = last_tshift;
345 last_tshift = tsample;
346
347 sample = tsample->fract;
348
349 update_end(&end, &sample, sizeof(sample), stop);
350 }
351
352 if (tracecmd_add_option(guest_ohandle, TRACECMD_OPTION_TIME_SHIFT, size, buf) == NULL)
353 die("Failed to add TIME SHIFT option");
354
355 free(buf);
356 }
357
add_tsc2nsec_to_guest(struct tracecmd_output * guest_ohandle,struct tracecmd_input * host_ihandle)358 static void add_tsc2nsec_to_guest(struct tracecmd_output *guest_ohandle,
359 struct tracecmd_input *host_ihandle)
360 {
361 unsigned long long offset;
362 int mult;
363 int shift;
364 int ret;
365 char buf[sizeof(int) * 2 + sizeof(long long)];
366 char *stop;
367 char *end;
368 int size = sizeof(buf);
369
370 ret = tracecmd_get_tsc2nsec(host_ihandle, &mult, &shift, &offset);
371 if (ret < 0)
372 die("Host does not have tsc2nsec info");
373
374 end = buf;
375 stop = buf + size;
376 update_end(&end, &mult, sizeof(mult), stop);
377 update_end(&end, &shift, sizeof(shift), stop);
378 update_end(&end, &offset, sizeof(offset), stop);
379
380 if (tracecmd_add_option(guest_ohandle, TRACECMD_OPTION_TSC2NSEC, size, buf) == NULL)
381 die("Failed to add TSC2NSEC option");
382
383 }
384
map_cpus(struct tracecmd_input * handle)385 static void map_cpus(struct tracecmd_input *handle)
386 {
387 int entry_ret;
388 int exit_ret;
389
390 entry_ret = tracecmd_follow_event(handle, "kvm", "kvm_entry", entry_callback, NULL);
391 exit_ret = tracecmd_follow_event(handle, "kvm", "kvm_exit", exit_callback, NULL);
392
393 if (entry_ret < 0 && exit_ret < 0)
394 die("Host needs kvm_exit or kvm_entry events to attach");
395
396 tracecmd_iterate_events(handle, NULL, 0, NULL, NULL);
397 }
398
trace_attach(int argc,char ** argv)399 void trace_attach(int argc, char **argv)
400 {
401 struct tracecmd_input *guest_ihandle;
402 struct tracecmd_input *host_ihandle;
403 struct tracecmd_output *guest_ohandle;
404 struct tracecmd_output *host_ohandle;
405 unsigned long long guest_id;
406 char *guest_file;
407 char *host_file;
408 int ret;
409 int fd;
410
411 for (;;) {
412 int c;
413
414 c = getopt(argc-1, argv+1, "c:s:h");
415 if (c == -1)
416 break;
417
418 switch (c) {
419 case 'h':
420 usage(argv);
421 break;
422 case 's':
423 add_timeshift(optarg);
424 break;
425 case 'c':
426 num_cpus = atoi(optarg);
427 break;
428 default:
429 usage(argv);
430 }
431 }
432
433 /* Account for "attach" */
434 optind++;
435
436 if ((argc - optind) < 3)
437 usage(argv);
438
439 host_file = argv[optind++];
440 guest_file = argv[optind++];
441
442 for (; optind < argc; optind++)
443 add_vcpu_pid(argv[optind]);
444
445
446 host_ihandle = tracecmd_open(host_file,TRACECMD_FL_LOAD_NO_PLUGINS );
447 guest_ihandle = tracecmd_open(guest_file,TRACECMD_FL_LOAD_NO_PLUGINS );
448
449 if (!host_ihandle)
450 die("Could not read %s\n", host_file);
451
452 if (!guest_ihandle)
453 die("Could not read %s\n", guest_file);
454
455 guest_id = tracecmd_get_traceid(guest_ihandle);
456 if (!guest_id)
457 die("Guest data file does not contain traceid");
458
459 map_cpus(host_ihandle);
460
461 ret = tracecmd_get_guest_cpumap(host_ihandle, guest_id,
462 NULL, NULL, NULL);
463 if (ret == 0) {
464 printf("Guest is already mapped in host (id=0x%llx) .. skipping ...\n",
465 guest_id);
466 } else {
467
468 fd = open(host_file, O_RDWR);
469 if (fd < 0)
470 die("Could not write %s", host_file);
471
472 host_ohandle = tracecmd_get_output_handle_fd(fd);
473 if (!host_ohandle)
474 die("Error setting up %s for write", host_file);
475
476 add_guest_to_host(host_ohandle, guest_ihandle);
477 tracecmd_output_close(host_ohandle);
478 }
479
480 fd = open(guest_file, O_RDWR);
481 if (fd < 0)
482 die("Could not write %s", guest_file);
483
484 guest_ohandle = tracecmd_get_output_handle_fd(fd);
485 if (!guest_ohandle)
486 die("Error setting up %s for write", guest_file);
487
488 add_timeshift_to_guest(guest_ohandle, host_ihandle);
489 add_tsc2nsec_to_guest(guest_ohandle, host_ihandle);
490
491 tracecmd_output_close(guest_ohandle);
492
493 tracecmd_close(guest_ihandle);
494 tracecmd_close(host_ihandle);
495
496 free_timeshifts();
497 free_vcpu_pids();
498
499 return;
500 }
501