David van Moolenbroek 521fa314e2 Add trace(1): the MINIX3 system call tracer
Change-Id: Ib970c8647409196902ed53d6e9631a1673a4ab2e
2014-11-04 21:46:31 +00:00

687 lines
17 KiB
C

#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,
&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)
{
int i, index;
for (i = 0; i < COUNT(call_table); i++) {
if (call_table[i]->endpt != ANY &&
call_table[i]->endpt != endpt)
continue;
if (call_nr < call_table[i]->base)
continue;
index = 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(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", "%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;
}