• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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