summaryrefslogtreecommitdiff
path: root/minix/usr.bin/trace/call.c
blob: 1479cfd5c3e71fd56a7e081f27d957a869531deb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707

#include "inc.h"

#include <minix/com.h>
#include <minix/callnr.h>
#include <minix/endpoint.h>

static const struct calls *call_table[] = {
	&pm_calls,
	&vfs_calls,
	&rs_calls,
	&mib_calls,
	&vm_calls,
	&ipc_calls,
};

/*
 * Find a call handler for the given endpoint, call number pair.  Return NULL
 * if no call handler for this call exists.
 */
static const struct call_handler *
find_handler(endpoint_t endpt, int call_nr)
{
	unsigned int i, index;

	for (i = 0; i < COUNT(call_table); i++) {
		if (call_table[i]->endpt != ANY &&
		    call_table[i]->endpt != endpt)
			continue;

		if ((unsigned int)call_nr < call_table[i]->base)
			continue;

		index = (unsigned int)call_nr - call_table[i]->base;

		if (index >= call_table[i]->count)
			continue;

		if (call_table[i]->map[index].outfunc == NULL)
			continue;

		return &call_table[i]->map[index];
	}

	return NULL;
}

/*
 * Print an endpoint.
 */
void
put_endpoint(struct trace_proc * proc, const char * name, endpoint_t endpt)
{
	const char *text = NULL;

	if (!valuesonly) {
		switch (endpt) {
		TEXT(ASYNCM);
		TEXT(IDLE);
		TEXT(CLOCK);
		TEXT(SYSTEM);
		TEXT(KERNEL);
		TEXT(PM_PROC_NR);
		TEXT(VFS_PROC_NR);
		TEXT(RS_PROC_NR);
		TEXT(MEM_PROC_NR);
		TEXT(SCHED_PROC_NR);
		TEXT(TTY_PROC_NR);
		TEXT(DS_PROC_NR);
		TEXT(MIB_PROC_NR);
		TEXT(VM_PROC_NR);
		TEXT(PFS_PROC_NR);
		TEXT(ANY);
		TEXT(NONE);
		TEXT(SELF);
		}
	}

	if (text != NULL)
		put_field(proc, name, text);
	else
		put_value(proc, name, "%d", endpt);
}

/*
 * Print a message structure.  The source field will be printed only if the
 * PF_ALT flag is given.
 */
static void
put_message(struct trace_proc * proc, const char * name, int flags,
	vir_bytes addr)
{
	message m;

	if (!put_open_struct(proc, name, flags, addr, &m, sizeof(m)))
		return;

	if (flags & PF_ALT)
		put_endpoint(proc, "m_source", m.m_source);

	put_value(proc, "m_type", "0x%x", m.m_type);

	put_close_struct(proc, FALSE /*all*/);
}

/*
 * Print the call's equals sign, which also implies that the parameters part of
 * the call has been fully printed and the corresponding closing parenthesis
 * may have to be printed, if it has not been printed already.
 */
void
put_equals(struct trace_proc * proc)
{

	/*
	 * Do not allow multiple equals signs on a single line.  This check is
	 * protection against badly written handlers.  It does not work for the
	 * no-return type, but such calls are rare and less error prone anyway.
	 */
	assert((proc->call_flags & (CF_DONE | CF_NORETURN)) != CF_DONE);

	/*
	 * We allow (and in fact force) handlers to call put_equals in order to
	 * indicate that the call's parameters block has ended, so we must end
	 * the block here, if we hadn't done so before.
	 */
	if (!(proc->call_flags & CF_DONE)) {
		put_close(proc, ") ");

		proc->call_flags |= CF_DONE;
	}

	put_align(proc);
	put_text(proc, "= ");

	format_set_sep(proc, NULL);
}

/*
 * Print the primary result of a call, after the equals sign.  It is always
 * possible that this is an IPC-level or other low-level error, in which case
 * this takes precedence, which is why this function must be called to print
 * the result if the call failed in any way at all; it may or may not be used
 * if the call succeeded.  For regular call results, default MINIX3/POSIX
 * semantics are used: if the return value is negative, the actual call failed
 * with -1 and the negative return value is the call's error code.  The caller
 * may consider other cases a failure (e.g., waitpid() returning 0), but
 * negative return values *not* signifying an error are currently not supported
 * since they are not present in MINIX3.
 */
void
put_result(struct trace_proc * proc)
{
	const char *errname;
	int value;

	/* This call should always be preceded by a put_equals call. */
	assert(proc->call_flags & CF_DONE);

	/*
	 * If we failed to copy in the result register or message, print a
	 * basic error and nothing else.
	 */
	if (proc->call_flags & (CF_REG_ERR | CF_MSG_ERR)) {
		put_text(proc, "<fault>");

		return;
	}

	/*
	 * If we are printing a system call rather than an IPC call, and an
	 * error occurred at the IPC level, prefix the output with "<ipc>" to
	 * indicate the IPC failure.  If we are printing an IPC call, an IPC-
	 * level result is implied, so we do not print this.
	 */
	if (proc->call_handler != NULL && (proc->call_flags & CF_IPC_ERR))
		put_text(proc, "<ipc> ");

	value = proc->call_result;

	if (value >= 0)
		put_fmt(proc, "%d", value);
	else if (!valuesonly && (errname = get_error_name(-value)) != NULL)
		put_fmt(proc, "-1 [%s]", errname);
	else
		put_fmt(proc, "-1 [%d]", -value);

	format_set_sep(proc, " ");
}

/*
 * The default enter-call (out) printer, which prints no parameters and is thus
 * immediately done with printing parameters.
 */
int
default_out(struct trace_proc * __unused proc, const message * __unused m_out)
{

	return CT_DONE;
}

/*
 * The default leave-call (in) printer, which simply prints the call result,
 * possibly preceded by an equals sign if none was printed yet.  For obvious
 * reasons, if the handler's out printer returned CT_NOTDONE, this default
 * printer must not be used.
 */
void
default_in(struct trace_proc * proc, const message * __unused m_out,
	const message * __unused m_in, int __unused failed)
{

	if ((proc->call_flags & (CF_DONE | CF_NORETURN)) != CF_DONE)
		put_equals(proc);
	put_result(proc);
}

/*
 * Prepare a sendrec call, by copying in the request message, determining
 * whether it is one of the calls that the tracing engine should know about,
 * searching for a handler for the call, and returning a name for the call.
 */
static const char *
sendrec_prepare(struct trace_proc * proc, endpoint_t endpt, vir_bytes addr,
	int * trace_class)
{
	const char *name;
	int r;

	r = mem_get_data(proc->pid, addr, &proc->m_out, sizeof(proc->m_out));

	if (r == 0) {
		if (endpt == PM_PROC_NR) {
			if (proc->m_out.m_type == PM_EXEC)
				*trace_class = TC_EXEC;
			else if (proc->m_out.m_type == PM_SIGRETURN)
				*trace_class = TC_SIGRET;
		}

		proc->call_handler = find_handler(endpt, proc->m_out.m_type);
	} else
		proc->call_handler = NULL;

	if (proc->call_handler != NULL) {
		if (proc->call_handler->namefunc != NULL)
			name = proc->call_handler->namefunc(&proc->m_out);
		else
			name = proc->call_handler->name;

		assert(name != NULL);
	} else
		name = "ipc_sendrec";

	return name;
}

/*
 * Print the outgoing (request) part of a sendrec call.  If we found a call
 * handler for the call, let the handler generate output.  Otherwise, print the
 * sendrec call at the kernel IPC level.  Return the resulting call flags.
 */
static unsigned int
sendrec_out(struct trace_proc * proc, endpoint_t endpt, vir_bytes addr)
{

	if (proc->call_handler != NULL) {
		return proc->call_handler->outfunc(proc, &proc->m_out);
	} else {
		put_endpoint(proc, "src_dest", endpt);
		/*
		 * We have already copied in the message, but if we used m_out
		 * and PF_LOCADDR here, a copy failure would cause "&.." to be
		 * printed rather than the actual message address.
		 */
		put_message(proc, "m_ptr", 0, addr);

		return CT_DONE;
	}
}

/*
 * Print the incoming (reply) part of a sendrec call.  Copy in the reply
 * message, determine whether the call is considered to have failed, and let
 * the call handler do the rest.  If no call handler was found, print an
 * IPC-level result.
 */
static void
sendrec_in(struct trace_proc * proc, int failed)
{
	message m_in;

	if (failed) {
		/* The call failed at the IPC level. */
		memset(&m_in, 0, sizeof(m_in)); /* not supposed to be used */
		assert(proc->call_flags & CF_IPC_ERR);
	} else if (mem_get_data(proc->pid, proc->m_addr, &m_in,
	    sizeof(m_in)) != 0) {
		/* The reply message is somehow unavailable to us. */
		memset(&m_in, 0, sizeof(m_in)); /* not supposed to be used */
		proc->call_result = EGENERIC; /* not supposed to be used */
		proc->call_flags |= CF_MSG_ERR;
		failed = PF_FAILED;
	} else {
		/* The result is for the actual call. */
		proc->call_result = m_in.m_type;
		failed = (proc->call_result < 0) ? PF_FAILED : 0;
	}

	if (proc->call_handler != NULL)
		proc->call_handler->infunc(proc, &proc->m_out, &m_in, failed);
	else
		put_result(proc);
}

/*
 * Perform preparations for printing a system call.  Return two things: the
 * name to use for the call, and the trace class of the call.
 * special treatment).
 */
static const char *
call_prepare(struct trace_proc * proc, reg_t reg[3], int * trace_class)
{

	switch (proc->call_type) {
	case SENDREC:
		return sendrec_prepare(proc, (endpoint_t)reg[1],
		    (vir_bytes)reg[2], trace_class);

	case SEND:
		return "ipc_send";

	case SENDNB:
		return "ipc_sendnb";

	case RECEIVE:
		return "ipc_receive";

	case NOTIFY:
		return "ipc_notify";

	case SENDA:
		return "ipc_senda";

	case MINIX_KERNINFO:
		return "minix_kerninfo";

	default:
		/*
		 * It would be nice to include the call number here, but we
		 * must return a string that will last until the entire call is
		 * finished.  Adding another buffer to the trace_proc structure
		 * is an option, but it seems overkill..
		 */
		return "ipc_unknown";
	}
}

/*
 * Print the outgoing (request) part of a system call.  Return the resulting
 * call flags.
 */
static unsigned int
call_out(struct trace_proc * proc, reg_t reg[3])
{

	switch (proc->call_type) {
	case SENDREC:
		proc->m_addr = (vir_bytes)reg[2];

		return sendrec_out(proc, (endpoint_t)reg[1],
		    (vir_bytes)reg[2]);

	case SEND:
	case SENDNB:
		put_endpoint(proc, "dest", (endpoint_t)reg[1]);
		put_message(proc, "m_ptr", 0, (vir_bytes)reg[2]);

		return CT_DONE;

	case RECEIVE:
		proc->m_addr = (vir_bytes)reg[2];

		put_endpoint(proc, "src", (endpoint_t)reg[1]);

		return CT_NOTDONE;

	case NOTIFY:
		put_endpoint(proc, "dest", (endpoint_t)reg[1]);

		return CT_DONE;

	case SENDA:
		put_ptr(proc, "table", (vir_bytes)reg[2]);
		put_value(proc, "count", "%zu", (size_t)reg[1]);

		return CT_DONE;

	case MINIX_KERNINFO:
	default:
		return CT_DONE;
	}
}

/*
 * Print the incoming (reply) part of a call.
 */
static void
call_in(struct trace_proc * proc, int failed)
{

	switch (proc->call_type) {
	case SENDREC:
		sendrec_in(proc, failed);

		break;

	case RECEIVE:
		/* Print the source as well. */
		put_message(proc, "m_ptr", failed | PF_ALT, proc->m_addr);
		put_equals(proc);
		put_result(proc);

		break;

	case MINIX_KERNINFO:
		/*
		 * We do not have a platform-independent means to access the
		 * secondary IPC return value, so we cannot print the receive
		 * status or minix_kerninfo address.
		 */
		/* FALLTHROUGH */
	default:
		put_result(proc);

		break;
	}
}

/*
 * Determine whether to skip printing the given call, based on its name.
 */
static int
call_hide(const char * __unused name)
{

	/*
	 * TODO: add support for such filtering, with an strace-like -e command
	 * line option.  For now, we filter nothing, although calls may still
	 * be hidden as the result of a register retrieval error.
	 */
	return FALSE;
}

/*
 * The given process entered a system call.  Return the trace class of the
 * call: TC_EXEC for an execve() call, TC_SIGRET for a sigreturn() call, or
 * TC_NORMAL for a call that requires no exceptions in the trace engine.
 */
int
call_enter(struct trace_proc * proc, int show_stack)
{
	const char *name;
	reg_t reg[3];
	int trace_class, type;

	/* Get the IPC-level type and parameters of the system call. */
	if (kernel_get_syscall(proc->pid, reg) < 0) {
		/*
		 * If obtaining the details of the system call failed, even
		 * though we know the process is stopped on a system call, we
		 * are going to assume that the process got killed somehow.
		 * Thus, the best we can do is ignore the system call entirely,
		 * and hope that the next thing we hear about this process is
		 * its termination.  At worst, we ignore a serious error..
		 */
		proc->call_flags = CF_HIDE;

		return FALSE;
	}

	/*
	 * Obtain the call name that is to be used for this call, and decide
	 * whether we want to print this call at all.
	 */
	proc->call_type = (int)reg[0];
	trace_class = TC_NORMAL;

	name = call_prepare(proc, reg, &trace_class);

	proc->call_name = name;

	if (call_hide(name)) {
		proc->call_flags = CF_HIDE;

		return trace_class;
	}

	/* Only print a stack trace if we are printing the call itself. */
	if (show_stack)
		kernel_put_stacktrace(proc);

	/*
	 * Start a new line, start recording, and print the call name and
	 * opening parenthesis.
	 */
	put_newline();

	format_reset(proc);

	record_start(proc);

	put_text(proc, name);
	put_open(proc, NULL, PF_NONAME, "(", ", ");

	/*
	 * Print the outgoing part of the call, that is, some or all of its
	 * parameters.  This call returns flags indicating how far printing
	 * got, and may be one of the following combinations:
	 * - CT_NOTDONE (0) if printing parameters is not yet complete; after
	 *   the call split, the in handler must print the rest itself;
	 * - CT_DONE (CF_DONE) if printing parameters is complete, and we
	 *   should now print the closing parenthesis and equals sign;
	 * - CT_NORETURN (CF_DONE|CF_NORETURN) if printing parameters is
	 *   complete, but we should not print the equals sign, because the
	 *   call is expected not to return (the no-return call type).
	 */
	type = call_out(proc, reg);
	assert(type == CT_NOTDONE || type == CT_DONE || type == CT_NORETURN);

	/*
	 * Print whatever the handler told us to print for now.
	 */
	if (type & CF_DONE) {
		if (type & CF_NORETURN) {
			put_close(proc, ")");

			put_space(proc);

			proc->call_flags |= type;
		} else {
			/*
			 * The equals sign is printed implicitly for the
			 * CT_DONE type only.  For CT_NORETURN and CT_NOTDONE,
			 * the "in" handler has to do it explicitly.
			 */
			put_equals(proc);
		}
	} else {
		/*
		 * If at least one parameter was printed, print the separator
		 * now.  We know that another parameter will follow (otherwise
		 * the caller would have returned CT_DONE), and this way the
		 * output looks better.
		 */
		format_push_sep(proc);
	}

	/*
	 * We are now at the call split; further printing will be done once the
	 * call returns, through call_leave.  Stop recording; if the call gets
	 * suspended and later resumed, we should replay everything up to here.
	 */
#if DEBUG
	put_text(proc, "|"); /* warning, this may push a space */
#endif

	record_stop(proc);

	output_flush();

	return trace_class;
}

/*
 * The given process left a system call, or if skip is set, the leave phase of
 * the current system call should be ended.
 */
void
call_leave(struct trace_proc * proc, int skip)
{
	reg_t retreg;
	int hide, failed;

	/* If the call is skipped, it must be a no-return type call. */
	assert(!skip || (proc->call_flags & (CF_NORETURN | CF_HIDE)));

	/*
	 * Start by replaying the current call, if necessary.  If the call was
	 * suspended and we are about to print the "in" part, this is obviously
	 * needed.  If the call is hidden, replaying will be a no-op, since
	 * nothing was recorded for this call.  The special case is a skipped
	 * call (which, as established above, must be a no-return call, e.g.
	 * exec), for which replaying has the effect that if the call was
	 * previously suspended, it will now be replayed, without suspension:
	 *
	 *       2| execve("./test", ["./test"], [..(12)]) <..>
	 *       3| sigsuspend([]) = <..>
	 * [A]   2| execve("./test", ["./test"], [..(12)])
	 *       2| ---
	 *       2| Tracing test (pid 2)
	 *
	 * The [A] line is the result of replaying the skipped call.
	 */
	call_replay(proc);

	hide = (proc->call_flags & CF_HIDE);

	if (!hide && !skip) {
		/* Get the IPC-level result of the call. */
		if (kernel_get_retreg(proc->pid, &retreg) < 0) {
			/* This should never happen.  Deal with it anyway. */
			proc->call_flags |= CF_REG_ERR;
			failed = PF_FAILED;
		} else if ((proc->call_result = (int)retreg) < 0) {
			proc->call_flags |= CF_IPC_ERR;
			failed = PF_FAILED;
		} else
			failed = 0;

		/*
		 * Print the incoming part of the call, that is, possibly some
		 * or all of its parameters and the call's closing parenthesis
		 * (if CT_NOTDONE), and the equals sign (if not CT_DONE), then
		 * the call result.
		 */
		call_in(proc, failed);
	}

	if (!hide) {
		/*
		 * The call is complete now, so clear the recording.  This also
		 * implies that no suspension marker will be printed anymore.
		 */
		record_clear(proc);

		put_newline();
	}

	/*
	 * For calls not of the no-return type, an equals sign must have been
	 * printed by now.  This is protection against badly written handlers.
	 */
	assert(proc->call_flags & CF_DONE);

	proc->call_name = NULL;
	proc->call_flags = 0;
}

/*
 * Replay the recorded text, if any, for the enter phase of the given process.
 * If there is no recorded text, start a new line anyway.
 */
void
call_replay(struct trace_proc * proc)
{

	/*
	 * We get TRUE if the recorded call should be replayed, but the
	 * recorded text for the call did not fit in the recording buffer.
	 * In that case, we have to come up with a replacement text for the
	 * call up to the call split.
	 */
	if (record_replay(proc) == TRUE) {
		/*
		 * We basically place a "<..>" suspension marker in the
		 * parameters part of the call, and use its call name and flags
		 * for the rest.  There is a trailing space in all cases.
		 */
		put_fmt(proc, "%s(<..>%s", proc->call_name,
		    !(proc->call_flags & CF_DONE) ? "," :
		    ((proc->call_flags & CF_NORETURN) ? ")" : ") ="));
		put_space(proc);
	}
}

/*
 * Return the human-readable name of the call currently being made by the given
 * process.  The process is guaranteed to be in a call, although the call may
 * be hidden.  Under no circumstances may this function return a NULL pointer.
 */
const char *
call_name(struct trace_proc * proc)
{

	assert(proc->call_name != NULL);

	return proc->call_name;
}

/*
 * Return whether the current call failed due to an error at the system call
 * level, and if so, return the error code as well.  May be called during the
 * leave phase of a call only.
 */
int
call_errno(struct trace_proc * proc, int * err)
{

	if (proc->call_flags & (CF_REG_ERR | CF_MSG_ERR | CF_IPC_ERR))
		return FALSE;

	if (proc->call_result >= 0)
		return FALSE;

	*err = -proc->call_result;
	return TRUE;
}