Add trace(1): the MINIX3 system call tracer
Change-Id: Ib970c8647409196902ed53d6e9631a1673a4ab2e
This commit is contained in:
parent
10b559663e
commit
521fa314e2
29 changed files with 9079 additions and 0 deletions
|
@ -522,6 +522,7 @@
|
||||||
./usr/bin/touch minix-sys
|
./usr/bin/touch minix-sys
|
||||||
./usr/bin/tput minix-sys
|
./usr/bin/tput minix-sys
|
||||||
./usr/bin/tr minix-sys
|
./usr/bin/tr minix-sys
|
||||||
|
./usr/bin/trace minix-sys
|
||||||
./usr/bin/true minix-sys
|
./usr/bin/true minix-sys
|
||||||
./usr/bin/truncate minix-sys
|
./usr/bin/truncate minix-sys
|
||||||
./usr/bin/tsort minix-sys
|
./usr/bin/tsort minix-sys
|
||||||
|
@ -2560,6 +2561,7 @@
|
||||||
./usr/man/man1/touch.1 minix-sys
|
./usr/man/man1/touch.1 minix-sys
|
||||||
./usr/man/man1/tput.1 minix-sys
|
./usr/man/man1/tput.1 minix-sys
|
||||||
./usr/man/man1/tr.1 minix-sys
|
./usr/man/man1/tr.1 minix-sys
|
||||||
|
./usr/man/man1/trace.1 minix-sys
|
||||||
./usr/man/man1/trap.1 minix-sys obsolete
|
./usr/man/man1/trap.1 minix-sys obsolete
|
||||||
./usr/man/man1/true.1 minix-sys
|
./usr/man/man1/true.1 minix-sys
|
||||||
./usr/man/man1/truncate.1 minix-sys
|
./usr/man/man1/truncate.1 minix-sys
|
||||||
|
|
|
@ -8,5 +8,6 @@ SUBDIR+= grep
|
||||||
SUBDIR+= ministat
|
SUBDIR+= ministat
|
||||||
SUBDIR+= top
|
SUBDIR+= top
|
||||||
SUBDIR+= toproto
|
SUBDIR+= toproto
|
||||||
|
SUBDIR+= trace
|
||||||
|
|
||||||
.include <bsd.subdir.mk>
|
.include <bsd.subdir.mk>
|
||||||
|
|
21
minix/usr.bin/trace/Makefile
Normal file
21
minix/usr.bin/trace/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.include <bsd.own.mk>
|
||||||
|
|
||||||
|
PROG= trace
|
||||||
|
SRCS= call.o error.o escape.o format.o ioctl.o kernel.o mem.o output.o \
|
||||||
|
proc.o signal.o trace.o
|
||||||
|
.PATH: ${.CURDIR}/service
|
||||||
|
SRCS+= pm.o vfs.o rs.o vm.o ipc.o
|
||||||
|
.PATH: ${.CURDIR}/ioctl
|
||||||
|
SRCS+= block.o char.o net.o svrctl.o
|
||||||
|
|
||||||
|
CPPFLAGS+= -D_MINIX_SYSTEM=1 -I${.CURDIR} -I${NETBSDSRCDIR}/minix
|
||||||
|
|
||||||
|
error.c: error.awk ${NETBSDSRCDIR}/sys/sys/errno.h
|
||||||
|
${TOOL_AWK} -f ${.ALLSRC} > ${.TARGET}
|
||||||
|
|
||||||
|
signal.c: signal.awk ${NETBSDSRCDIR}/sys/sys/signal.h
|
||||||
|
${TOOL_AWK} -f ${.ALLSRC} > ${.TARGET}
|
||||||
|
|
||||||
|
CLEANFILES+= error.c signal.c
|
||||||
|
|
||||||
|
.include <bsd.prog.mk>
|
255
minix/usr.bin/trace/NOTES
Normal file
255
minix/usr.bin/trace/NOTES
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
Developer notes regarding trace(1), by David van Moolenbroek.
|
||||||
|
|
||||||
|
|
||||||
|
OVERALL CODE STRUCTURE
|
||||||
|
|
||||||
|
The general tracing engine is in trace.c. It passes IPC-level system call
|
||||||
|
enter and leave events off to call.c, which handles IPC-level system call
|
||||||
|
printing and passes off system calls to be interpreted by a service-specific
|
||||||
|
system call handler whenever possible. All the service-specific code is in the
|
||||||
|
service/ subdirectory, grouped by destination service. IOCTLs are a special
|
||||||
|
case, which are handled in ioctl.c and passed on to driver-type-grouped IOCTL
|
||||||
|
handlers in the ioctl/ subdirectory (this grouping is not strict). Some of the
|
||||||
|
generated output goes through the formatting code in format.c, and all of it
|
||||||
|
ends up in output.c. The remaining source files contain support code.
|
||||||
|
|
||||||
|
|
||||||
|
ADDING A SYSTEM CALL HANDLER
|
||||||
|
|
||||||
|
In principle, every system call stops the traced process twice: once when the
|
||||||
|
system call is started (the call-enter event) and once when the system call
|
||||||
|
returns (the call-leave event). The tracer uses the call-enter event to print
|
||||||
|
the request being made, and the call-leave event to print the result of the
|
||||||
|
call. The output format is supposed to mimic largely what the system call
|
||||||
|
looks like from a C program, although with additional information where that
|
||||||
|
makes sense. The general output format for system calls is:
|
||||||
|
|
||||||
|
name(parameters) = result
|
||||||
|
|
||||||
|
..where "name" is the name of the system call, "parameters" is a list of system
|
||||||
|
call parameters, and "result" is the result of the system call. If possible,
|
||||||
|
the part up to and including the equals sign is printed from the call-enter
|
||||||
|
event, and the result is printed from the call-leave event. However, many
|
||||||
|
system calls actually pass a pointer to a block of memory that is filled with
|
||||||
|
meaningful content as part of the system call. For that reason, it is also
|
||||||
|
possible that the call-enter event stops printing somewhere inside the
|
||||||
|
parameters block, and the call-leave event prints the rest of the parameters,
|
||||||
|
as well as the equals sign and the result after it. The place in the printed
|
||||||
|
system call where the call-enter printer stops and the call-leave printer is
|
||||||
|
supposed to pick up again, is referred to as the "call split".
|
||||||
|
|
||||||
|
The tracer has to a handler structure for every system call that can be made by
|
||||||
|
a user program to any of the the MINIX3 services. This handler structure
|
||||||
|
provides three elements: the name of the system call, an "out" function that
|
||||||
|
handles printing of the call-enter part of the system call, and an "in"
|
||||||
|
function that handles printing of the call-leave part of the system call. The
|
||||||
|
"out" function is expected to print zero or more call parameters, and then
|
||||||
|
return a call type, which indicates whether all parameters have been printed
|
||||||
|
yet, or not. In fact, there are three call types, shown here with an example
|
||||||
|
which has a "|" pipe symbol added to indicate the call split:
|
||||||
|
|
||||||
|
CT_DONE: write(5, "foo", 3) = |3
|
||||||
|
CT_NOTDONE: read(5, |"foo", 1024) = 3
|
||||||
|
CT_NORETURN: execve("foo", ["foo"], []")| = -1 [ENOENT]
|
||||||
|
|
||||||
|
The CT_DONE call type indicates that the handler is done printing all the
|
||||||
|
parameters during the call-enter event, and the call split will be after the
|
||||||
|
equals sign. The CT_NOTDONE call type indicates that the handler is not done
|
||||||
|
printing all parameters yet, thus yielding a call split in the middle of the
|
||||||
|
parameters block (or even right after the opening parenthesis). The no-return
|
||||||
|
(CT_NORETURN) call type is used for a small number of functions that do not
|
||||||
|
return on success. Currently, these are the exit(), execve(), and sigreturn()
|
||||||
|
system calls. For these calls, no result will be printed at all, unless such
|
||||||
|
a call fails, in which case a failure result is printed after all. The call
|
||||||
|
split is such that the entire parameters block is printed upon entering the
|
||||||
|
call, but the equals sign and result are printed only if the call does return.
|
||||||
|
|
||||||
|
Now more about the handler structure for the system call. First of all, each
|
||||||
|
system call has a name, which must be a static string. It may be supplied
|
||||||
|
either as a string, or as a function that returns a name string. The latter is
|
||||||
|
for cases where one message-level system call is used to implement multiple
|
||||||
|
C-level system calls (such as setitimer() and getitimer() both going through
|
||||||
|
PM_ITIMER). The name function has the following prototype:
|
||||||
|
|
||||||
|
const char *svc_syscall_name(const message *m_out);
|
||||||
|
|
||||||
|
..where "m_out" is a local copy of the request message, which the name function
|
||||||
|
can use to decide what string to return for the system call. As a sidenote,
|
||||||
|
in the future, the system call name will be used to implement call filtering.
|
||||||
|
|
||||||
|
An "out" printer function has the following prototype:
|
||||||
|
|
||||||
|
int svc_syscall_out(struct trace_proc *proc, const message *m_out);
|
||||||
|
|
||||||
|
Here, "proc" is a pointer to the process structure containing information about
|
||||||
|
the process making the system call; proc->pid returns the process PID, but the
|
||||||
|
function should not access any other fields of this structure directly.
|
||||||
|
Instead, many of the output primitive and helper functions (which are all
|
||||||
|
prefixed with "put_") take this pointer as part of the call. "m_out" is a
|
||||||
|
local copy of the request message, and the printer may access its fields as it
|
||||||
|
sees fit.
|
||||||
|
|
||||||
|
The printer function should simply print parameters. The call name and the
|
||||||
|
opening parenthesis are printed by the main output routine.
|
||||||
|
|
||||||
|
All simple call parameters should be printed using the put_field() and
|
||||||
|
put_value() functions. The former prints a parameter or field name as flat
|
||||||
|
text; the latter is a printf-like interface to the former. By default, call
|
||||||
|
paramaters are simply printed as "value", but if printing all names is enabled,
|
||||||
|
call parameters are printed as "name=value". Thus, all parameters should be
|
||||||
|
given a name, even if this name does not show up by default. Either way, these
|
||||||
|
two functions take care of deciding whether to print the name, as well as of
|
||||||
|
printing separators between the parameters. More about printing more complex
|
||||||
|
parameters (such as structures) in a bit.
|
||||||
|
|
||||||
|
The out printer function must return one of the three CT_ call type values. If
|
||||||
|
it returns CT_DONE, the main output routine will immediately print the closing
|
||||||
|
parenthesis and equals sign. If it returns CF_NORETURN, a closing parenthesis
|
||||||
|
will be printed. If it return CF_NOTDONE, only a parameter field separator
|
||||||
|
(that is, a comma and a space) will be printed--after all, it can be assumed
|
||||||
|
that more parameters will be printed later.
|
||||||
|
|
||||||
|
An "in" printer function has the following prototype:
|
||||||
|
|
||||||
|
void svc_syscall_in(struct trace_proc *proc, const message *m_out,
|
||||||
|
const message *m_in, int failed);
|
||||||
|
|
||||||
|
Again, "proc" is the traced process of which its current system call has now
|
||||||
|
returned. "m_out" is again the request message, guaranteed to be unchanged
|
||||||
|
since the "out" call. "m_in" is the reply message from the service. "failed"
|
||||||
|
is either 0 to indicate that the call appears to have succeeded, or PF_FAILED
|
||||||
|
to indicate that the call definitely failed. If PF_FAILED is set, the call
|
||||||
|
has failed either at the IPC level or at the system call level (or for another,
|
||||||
|
less common reason). In that case, the contents of "m_in" may be garbage and
|
||||||
|
"m_in" must not be used at all.
|
||||||
|
|
||||||
|
For CF_NOTDONE type calls, the in printer function should first print the
|
||||||
|
remaining parameters. Here especially, it is important to consider that the
|
||||||
|
entire call may fail. In that case, the parameters of which the contents were
|
||||||
|
still going to be printed may also contain garbage, since they were never
|
||||||
|
filled. The expected behavior is to print such parameters as pointer or "&.."
|
||||||
|
or something else to indicate that their actual contents are not valid.
|
||||||
|
|
||||||
|
Either way, once a CF_NOTDONE type call function is done printing the remaining
|
||||||
|
parameters, it must call put_equals(proc) to print the closing parenthesis of
|
||||||
|
the call and the equals sign. CF_NORETURN calls must also use put_equals(proc)
|
||||||
|
to print the equals sign.
|
||||||
|
|
||||||
|
Then comes the result part. If the call failed, the in printer function *must*
|
||||||
|
use put_result(proc) to print the failure result. This call not only takes
|
||||||
|
care of converting negative error codes from m_in->m_type into "-1 [ECODE]" but
|
||||||
|
also prints appropriate failure codes for IPC-level and other exceptional
|
||||||
|
failures. Only if the system call did not fail, may the in printer function
|
||||||
|
choose to not call put_result(proc), which on success simply prints
|
||||||
|
m_in->m_type as an integer. Similarly, if the system call succeeded, the in
|
||||||
|
printer function may print extended results after the primary result, generally
|
||||||
|
in parentheses. For example, getpid() and getppid() share the same system call
|
||||||
|
and thus the tracer prints both return values, one as the primary result of the
|
||||||
|
actual call and one in parentheses with a clarifying name as extended result:
|
||||||
|
|
||||||
|
getpid() = 3 (ppid=1)
|
||||||
|
|
||||||
|
It should now be clear that printing extended results makes no sense if the
|
||||||
|
system call failed.
|
||||||
|
|
||||||
|
Besidse put_equals and put_result, the following more or less generic support
|
||||||
|
functions are available to print the various parts of the requests and replies.
|
||||||
|
|
||||||
|
put_field - output a parameter, structure field, and so on; this function
|
||||||
|
should be used for just about every actual value
|
||||||
|
put_value - printf-like version of put_field
|
||||||
|
put_text - output plain text; for call handlers, this should be used only to
|
||||||
|
to add things right after a put_field call, never on its own
|
||||||
|
put_fmt - printf-like version of put_text, should generally not be used
|
||||||
|
from call handlers at all
|
||||||
|
put_open - open a nested block of fields, surrounded by parentheses,
|
||||||
|
brackets, or something like that; this is used for structures,
|
||||||
|
arrays, and any other similar nontrivial case of nesting
|
||||||
|
put_close - close a previously opened block of fields; the nesting depth is
|
||||||
|
actually tracked (to keep per-level separators etc), so each
|
||||||
|
put_open call must have a corresponding put_close call
|
||||||
|
put_open_struct - perform several tasks necessary to start printing the
|
||||||
|
fields of a structure; note that this function may fail!
|
||||||
|
put_close_struct - end successful printing of a structure
|
||||||
|
put_ptr - print a pointer in the traced process
|
||||||
|
put_buf - print a buffer or string
|
||||||
|
put_flags - print a bitwise flags field
|
||||||
|
put_tail - helper function for printing the continuation part of an array
|
||||||
|
|
||||||
|
Many of these support functions take a flags field which takes PF_-prefixed
|
||||||
|
flags to modify the output they generate. The value of 'failed' in the in
|
||||||
|
printer function may actually be passed (bitwise-OR'ed in) as the PF_FAILED
|
||||||
|
flag to these support functions, and they will do the right thing. For
|
||||||
|
example, a call to put_open_struct with the PF_FAILED flag will end up simply
|
||||||
|
printing the pointer to the structure, and not allow printing of the contents
|
||||||
|
of the structure.
|
||||||
|
|
||||||
|
The above support functions are documented (at a basic level) within the code,
|
||||||
|
but in many cases, it may be useful to look up how they are used in practice by
|
||||||
|
the existing handlers. The same goes for various less clear cases; while there
|
||||||
|
is basic support for printing structures, support for printing arrays must be
|
||||||
|
coded fully by hand, as has been done for many places. A serious attempt has
|
||||||
|
been made to make the output consistent across the board (mainly thanks to the
|
||||||
|
output format of strace, on which the output of this tracer has been based,
|
||||||
|
sometimes very strictly and sometimes more loosely, but that aside) so it is
|
||||||
|
always advisable to follow the ways of the existing handlers. Also keep in
|
||||||
|
mind that there are already printer functions for several generic structures,
|
||||||
|
and these should be used whenever possible (e.g., see the put_fd() comment).
|
||||||
|
|
||||||
|
Finally, the default_out and default_in functions may be used as printer
|
||||||
|
functions for call with no parameters, and for functions which need no more
|
||||||
|
than put_result() to print their system call result, respectively.
|
||||||
|
|
||||||
|
|
||||||
|
INTERNALS: MULTIPROCESS OUTPUT AND PREEMPTION
|
||||||
|
|
||||||
|
Things get interesting when multiple processes are traced at once. Due to the
|
||||||
|
nature of process scheduling, system calls may end up being preempted between
|
||||||
|
the call-enter and call-leave phases. This means that the output of a system
|
||||||
|
call has to be suspended to give way to an event from another traced process.
|
||||||
|
Such preemption may occur with literally all calls; not just "blocking" calls.
|
||||||
|
|
||||||
|
The tracer goes through some lengths to aid the user in following the output in
|
||||||
|
the light of preemtion. The most important aspect is that the output of the
|
||||||
|
call-enter phase is recorded, so that in the case of preemption, the call-leave
|
||||||
|
phase can start by replaying the record. As a result, the user gets to see the
|
||||||
|
whole system call on a single line, instead of just the second half. Such
|
||||||
|
system call resumptions are marked with a "*" in their prefix, to show that
|
||||||
|
the call was not just entered. The output therefore looks like this:
|
||||||
|
|
||||||
|
2| syscall() = <..>
|
||||||
|
3| othercall() = 0
|
||||||
|
2|*syscall() = 0
|
||||||
|
|
||||||
|
Signals that arrive during a call will cause a resumption of the call as well.
|
||||||
|
As a result, a call may be resumed multiple times:
|
||||||
|
|
||||||
|
2| syscall() = <..>
|
||||||
|
3| othercall() = 0
|
||||||
|
2|*syscall() = ** SIGUSR1 ** ** SIGUSR2 ** <..>
|
||||||
|
3| othercall() = -1 [EBUSY]
|
||||||
|
2|*syscall() = ** SIGHUP ** <..>
|
||||||
|
3| othercall() = 0
|
||||||
|
2|*syscall() = 0
|
||||||
|
|
||||||
|
This entire scenario shows one single system call from process 2.
|
||||||
|
|
||||||
|
In the current implementation, the output that should be recorded and/or cause
|
||||||
|
the "<..>" preemption marker, as well as the cases where the recorded text must
|
||||||
|
be replayed, are marked by the code explicitly. Replay takes place in three
|
||||||
|
cases: upon the call-leave event (obviously), upon receiving a signal (as shown
|
||||||
|
above), and when it is required that a suspended no-return call is shown as
|
||||||
|
completed before continuing with other output. The last case applies to exit()
|
||||||
|
and execve(), and both are documented in the code quite extensively. Generally
|
||||||
|
speaking, in all output lines where no recording or replay actions are
|
||||||
|
performed, the recording will not be replayed but also not removed. This
|
||||||
|
allows for intermediate lines for that process in the output. Practically
|
||||||
|
speaking, future support for job control could even print when a process get
|
||||||
|
stopped and continued, for that process, while preempting the output for the
|
||||||
|
ongoing system call for that same process.
|
||||||
|
|
||||||
|
It is possible that the output of the call-enter phase exhausts the recording
|
||||||
|
buffer for its process. In this case, a new, shorter text is generated upon
|
||||||
|
process resumption. There are many other aspects to proper output formatting
|
||||||
|
in the light of preemption, but most of them should be documented as part of
|
||||||
|
the code reasonably well.
|
686
minix/usr.bin/trace/call.c
Normal file
686
minix/usr.bin/trace/call.c
Normal file
|
@ -0,0 +1,686 @@
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
28
minix/usr.bin/trace/error.awk
Normal file
28
minix/usr.bin/trace/error.awk
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Derived from libc errlist.awk
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
printf("/* This file is automatically generated by error.awk */\n\n");
|
||||||
|
printf("#include \"inc.h\"\n\n");
|
||||||
|
printf("static const char *const errors[] = {\n");
|
||||||
|
}
|
||||||
|
/^#define/ {
|
||||||
|
name = $2;
|
||||||
|
if (name == "ELAST")
|
||||||
|
next;
|
||||||
|
number = $3;
|
||||||
|
if (number == "(_SIGN")
|
||||||
|
number = $4;
|
||||||
|
if (number < 0 || number == "EAGAIN")
|
||||||
|
next;
|
||||||
|
printf("\t[%s] = \"%s\",\n", name, name);
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
printf("};\n\n");
|
||||||
|
printf("const char *\nget_error_name(int err)\n{\n\n");
|
||||||
|
printf("\tif (err >= 0 && err < sizeof(errors) / sizeof(errors[0]) &&\n");
|
||||||
|
printf("\t errors[err] != NULL)\n");
|
||||||
|
printf("\t\treturn errors[err];\n");
|
||||||
|
printf("\telse\n");
|
||||||
|
printf("\t\treturn NULL;\n");
|
||||||
|
printf("}\n");
|
||||||
|
}
|
48
minix/usr.bin/trace/escape.c
Normal file
48
minix/usr.bin/trace/escape.c
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
static const char *const escape[256] = {
|
||||||
|
"\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07",
|
||||||
|
"\\x08", "\\t", "\\n", "\\x0B", "\\x0C", "\\r", "\\x0E", "\\x0F",
|
||||||
|
"\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
|
||||||
|
"\\x18", "\\x19", "\\x1A", "\\x1B", "\\x1C", "\\x1D", "\\x1E", "\\x1F",
|
||||||
|
" ", "!", "\\\"", "#", "$", "%", "&", "'",
|
||||||
|
"(", ")", "*", "+", ",", "-", ".", "/",
|
||||||
|
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||||
|
"8", "9", ":", ";", "<", "=", ">", "?",
|
||||||
|
"@", "A", "B", "C", "D", "E", "F", "G",
|
||||||
|
"H", "I", "J", "K", "L", "M", "N", "O",
|
||||||
|
"P", "Q", "R", "S", "T", "U", "V", "W",
|
||||||
|
"X", "Y", "Z", "[", "\\", "]", "^", "_",
|
||||||
|
"`", "a", "b", "c", "d", "e", "f", "g",
|
||||||
|
"h", "i", "j", "k", "l", "m", "n", "o",
|
||||||
|
"p", "q", "r", "s", "t", "u", "v", "w",
|
||||||
|
"x", "y", "z", "{", "|", "}", "~", "\\x7F",
|
||||||
|
"\\x80", "\\x81", "\\x82", "\\x83", "\\x84", "\\x85", "\\x86", "\\x87",
|
||||||
|
"\\x88", "\\x89", "\\x8A", "\\x8B", "\\x8C", "\\x8D", "\\x8E", "\\x8F",
|
||||||
|
"\\x90", "\\x91", "\\x92", "\\x93", "\\x94", "\\x95", "\\x96", "\\x97",
|
||||||
|
"\\x98", "\\x99", "\\x9A", "\\x9B", "\\x9C", "\\x9D", "\\x9E", "\\x9F",
|
||||||
|
"\\xA0", "\\xA1", "\\xA2", "\\xA3", "\\xA4", "\\xA5", "\\xA6", "\\xA7",
|
||||||
|
"\\xA8", "\\xA9", "\\xAA", "\\xAB", "\\xAC", "\\xAD", "\\xAE", "\\xAF",
|
||||||
|
"\\xB0", "\\xB1", "\\xB2", "\\xB3", "\\xB4", "\\xB5", "\\xB6", "\\xB7",
|
||||||
|
"\\xB8", "\\xB9", "\\xBA", "\\xBB", "\\xBC", "\\xBD", "\\xBE", "\\xBF",
|
||||||
|
"\\xC0", "\\xC1", "\\xC2", "\\xC3", "\\xC4", "\\xC5", "\\xC6", "\\xC7",
|
||||||
|
"\\xC8", "\\xC9", "\\xCA", "\\xCB", "\\xCC", "\\xCD", "\\xCE", "\\xCF",
|
||||||
|
"\\xD0", "\\xD1", "\\xD2", "\\xD3", "\\xD4", "\\xD5", "\\xD6", "\\xD7",
|
||||||
|
"\\xD8", "\\xD9", "\\xDA", "\\xDB", "\\xDC", "\\xDD", "\\xDE", "\\xDF",
|
||||||
|
"\\xE0", "\\xE1", "\\xE2", "\\xE3", "\\xE4", "\\xE5", "\\xE6", "\\xE7",
|
||||||
|
"\\xE8", "\\xE9", "\\xEA", "\\xEB", "\\xEC", "\\xED", "\\xEE", "\\xEF",
|
||||||
|
"\\xF0", "\\xF1", "\\xF2", "\\xF3", "\\xF4", "\\xF5", "\\xF6", "\\xF7",
|
||||||
|
"\\xF8", "\\xF9", "\\xFA", "\\xFB", "\\xFC", "\\xFD", "\\xFE", "\\xFF",
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the given character, return a string representing an escaped version of
|
||||||
|
* the character.
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
get_escape(char c)
|
||||||
|
{
|
||||||
|
|
||||||
|
return escape[(unsigned int)(unsigned char)c];
|
||||||
|
}
|
426
minix/usr.bin/trace/format.c
Normal file
426
minix/usr.bin/trace/format.c
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The size of the formatting buffer, which in particular limits the maximum
|
||||||
|
* size of the output from the variadic functions. All printer functions which
|
||||||
|
* are dealing with potentially large or even unbounded output, should be able
|
||||||
|
* to generate their output in smaller chunks. In the end, nothing that is
|
||||||
|
* being printed as a unit should even come close to reaching this limit.
|
||||||
|
*/
|
||||||
|
#define FORMAT_BUFSZ 4096
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The buffer which is used for all intermediate copying and/or formatting.
|
||||||
|
* Care must be taken that only one function uses this buffer at any time.
|
||||||
|
*/
|
||||||
|
static char formatbuf[FORMAT_BUFSZ];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset the line formatting for the given process.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
format_reset(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
proc->next_sep = NULL;
|
||||||
|
proc->depth = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the next separator for the given process. The given separator may be
|
||||||
|
* NULL.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
format_set_sep(struct trace_proc * proc, const char * sep)
|
||||||
|
{
|
||||||
|
|
||||||
|
proc->next_sep = sep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print and clear the next separator for the process, if any.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
format_push_sep(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (proc->next_sep != NULL) {
|
||||||
|
put_text(proc, proc->next_sep);
|
||||||
|
|
||||||
|
proc->next_sep = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a field, e.g. a parameter or a field from a structure, separated from
|
||||||
|
* other fields at the same nesting depth as appropriate. If the given field
|
||||||
|
* name is not NULL, it may or may not be printed. The given text is what will
|
||||||
|
* be printed for this field so far, but the caller is allowed to continue
|
||||||
|
* printing text for the same field with e.g. put_text(). As such, the given
|
||||||
|
* text may even be an empty string.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_field(struct trace_proc * proc, const char * name, const char * text)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At depth -1 (the basic line level), names are not used. A name
|
||||||
|
* should not be supplied by the caller in that case, but, it happens.
|
||||||
|
*/
|
||||||
|
if (proc->depth < 0)
|
||||||
|
name = NULL;
|
||||||
|
|
||||||
|
format_push_sep(proc);
|
||||||
|
|
||||||
|
if (name != NULL && (proc->depths[proc->depth].name || allnames)) {
|
||||||
|
put_text(proc, name);
|
||||||
|
put_text(proc, "=");
|
||||||
|
}
|
||||||
|
|
||||||
|
put_text(proc, text);
|
||||||
|
|
||||||
|
format_set_sep(proc, proc->depths[proc->depth].sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Increase the nesting depth with a new block of fields, enclosed within
|
||||||
|
* parentheses, brackets, etcetera. The given name, which may be NULL, is the
|
||||||
|
* name of the entire nested block. In the flags field, PF_NONAME indicates
|
||||||
|
* that the fields within the block should have their names printed or not,
|
||||||
|
* although this may be overridden by setting the allnames variable. The given
|
||||||
|
* string is the block opening string (e.g., an opening parenthesis). The
|
||||||
|
* given separator is used to separate the fields within the nested block, and
|
||||||
|
* should generally be ", " to maintain output consistency.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_open(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
const char * string, const char * sep)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_field(proc, name, string);
|
||||||
|
|
||||||
|
proc->depth++;
|
||||||
|
|
||||||
|
assert(proc->depth < MAX_DEPTH);
|
||||||
|
|
||||||
|
proc->depths[proc->depth].sep = sep;
|
||||||
|
proc->depths[proc->depth].name = !(flags & PF_NONAME);
|
||||||
|
|
||||||
|
format_set_sep(proc, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decrease the nesting depth by ending a nested block of fields. The given
|
||||||
|
* string is the closing parenthesis, bracket, etcetera.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_close(struct trace_proc * proc, const char * string)
|
||||||
|
{
|
||||||
|
|
||||||
|
assert(proc->depth >= 0);
|
||||||
|
|
||||||
|
put_text(proc, string);
|
||||||
|
|
||||||
|
proc->depth--;
|
||||||
|
|
||||||
|
if (proc->depth >= 0)
|
||||||
|
format_set_sep(proc, proc->depths[proc->depth].sep);
|
||||||
|
else
|
||||||
|
format_set_sep(proc, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Version of put_text with variadic arguments. The given process may be NULL.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_fmt(struct trace_proc * proc, const char * fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
(void)vsnprintf(formatbuf, sizeof(formatbuf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
put_text(proc, formatbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Version of put_field with variadic arguments.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_value(struct trace_proc * proc, const char * name, const char * fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
(void)vsnprintf(formatbuf, sizeof(formatbuf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
put_field(proc, name, formatbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start printing a structure. In general, the function copies the contents of
|
||||||
|
* the structure of size 'size' from the traced process at 'addr' into the
|
||||||
|
* local 'ptr' structure, opens a nested block with name 'name' (which may
|
||||||
|
* be NULL) using an opening bracket, and returns TRUE to indicate that the
|
||||||
|
* caller should print fields from the structure. However, if 'flags' contains
|
||||||
|
* PF_FAILED, the structure will be printed as a pointer, no copy will be made,
|
||||||
|
* and the call will return FALSE. Similarly, if the remote copy fails, a
|
||||||
|
* pointer will be printed and the call will return FALSE. If PF_LOCADDR is
|
||||||
|
* given, 'addr' is a local address, and an intraprocess copy will be made.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
put_open_struct(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr, void * ptr, size_t size)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((flags & PF_FAILED) || valuesonly > 1 || addr == 0) {
|
||||||
|
if (flags & PF_LOCADDR)
|
||||||
|
put_field(proc, name, "&..");
|
||||||
|
else
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(flags & PF_LOCADDR)) {
|
||||||
|
if (mem_get_data(proc->pid, addr, ptr, size) < 0) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
memcpy(ptr, (void *) addr, size);
|
||||||
|
|
||||||
|
put_open(proc, name, flags, "{", ", ");
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End printing a structure. This must be called only to match a successful
|
||||||
|
* call to put_open_struct. The given 'all' flag indicates whether all fields
|
||||||
|
* of the structure have been printed; if not, a ".." continuation text is
|
||||||
|
* printed to show the user that some structure fields have not been printed.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_close_struct(struct trace_proc * proc, int all)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!all)
|
||||||
|
put_field(proc, NULL, "..");
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a pointer. NULL is treated as a special case.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_ptr(struct trace_proc * proc, const char * name, vir_bytes addr)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (addr == 0 && !valuesonly)
|
||||||
|
put_field(proc, name, "NULL");
|
||||||
|
else
|
||||||
|
put_value(proc, name, "&0x%lx", addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print the contents of a buffer, at remote address 'addr' and of 'bytes'
|
||||||
|
* size, as a field using name 'name' (which may be NULL). If the PF_FAILED
|
||||||
|
* flag is given, the buffer address is printed instead, since it is assumed
|
||||||
|
* that the actual buffer contains garbage. If the PF_LOCADDR flag is given,
|
||||||
|
* the given address is a local address and no intraprocess copies are
|
||||||
|
* performed. If the PF_STRING flag is given, the buffer is expected to
|
||||||
|
* contain a null terminator within its size, and the string will be printed
|
||||||
|
* only up to there. Normally, the string is cut off beyond a number of bytes
|
||||||
|
* which depends on the verbosity level; if the PF_FULL flag is given, the full
|
||||||
|
* string will be printed no matter its size (used mainly for path names, which
|
||||||
|
* typically become useless once cut off).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_buf(struct trace_proc * proc, const char * name, int flags, vir_bytes addr,
|
||||||
|
ssize_t size)
|
||||||
|
{
|
||||||
|
const char *escaped;
|
||||||
|
size_t len, off, max, chunk;
|
||||||
|
int i, cutoff;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if ((flags & PF_FAILED) || valuesonly || addr == 0 || size < 0) {
|
||||||
|
if (flags & PF_LOCADDR)
|
||||||
|
put_field(proc, name, "&..");
|
||||||
|
else
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
put_field(proc, name, "\"\"");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: the maximum says nothing about the size of the printed text.
|
||||||
|
* Escaped-character printing can make the output much longer. Does it
|
||||||
|
* make more sense to apply a limit after the escape transformation?
|
||||||
|
*/
|
||||||
|
if (verbose == 0) max = 32;
|
||||||
|
else if (verbose == 1) max = 256;
|
||||||
|
else max = SIZE_MAX;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the output is cut off, we put two dots after the closing quote.
|
||||||
|
* For non-string buffers, the output is cut off if the size exceeds
|
||||||
|
* our limit or we run into a copying error somewhere in the middle.
|
||||||
|
* For strings, the output is cut off unless we find a null terminator.
|
||||||
|
*/
|
||||||
|
cutoff = !!(flags & PF_STRING);
|
||||||
|
len = (size_t)size;
|
||||||
|
if (!(flags & PF_FULL) && len > max) {
|
||||||
|
len = max;
|
||||||
|
cutoff = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (off = 0; off < len; off += chunk) {
|
||||||
|
chunk = len - off;
|
||||||
|
if (chunk > sizeof(formatbuf) - 1)
|
||||||
|
chunk = sizeof(formatbuf) - 1;
|
||||||
|
|
||||||
|
if (!(flags & PF_LOCADDR)) {
|
||||||
|
if (mem_get_data(proc->pid, addr + off, formatbuf,
|
||||||
|
chunk) < 0) {
|
||||||
|
if (off == 0) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cutoff = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
memcpy(formatbuf, (void *)addr, chunk);
|
||||||
|
|
||||||
|
if (off == 0)
|
||||||
|
put_field(proc, name, "\"");
|
||||||
|
|
||||||
|
/* In strings, look for the terminating null character. */
|
||||||
|
if ((flags & PF_STRING) &&
|
||||||
|
(p = memchr(formatbuf, '\0', chunk)) != NULL) {
|
||||||
|
chunk = (size_t)(p - formatbuf);
|
||||||
|
cutoff = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print the buffer contents using escaped characters. */
|
||||||
|
for (i = 0; i < chunk; i++) {
|
||||||
|
escaped = get_escape(formatbuf[i]);
|
||||||
|
|
||||||
|
put_text(proc, escaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop if we found the end of the string. */
|
||||||
|
if ((flags & PF_STRING) && !cutoff)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cutoff)
|
||||||
|
put_text(proc, "\"..");
|
||||||
|
else
|
||||||
|
put_text(proc, "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a flags field, using known flag names. The name of the whole field is
|
||||||
|
* given as 'name' and may be NULL. The caller must supply an array of known
|
||||||
|
* flags as 'fp' (with 'num' entries). Each entry in the array has a mask, a
|
||||||
|
* value, and a name. If the given flags 'value', bitwise-ANDed with the mask
|
||||||
|
* of an entry, yields the value of that entry, then the name is printed. This
|
||||||
|
* means that certain zero bits may also be printed as actual flags, and that
|
||||||
|
* by supplying an all-bits-set mask can print a flag name for a zero value,
|
||||||
|
* for example F_OK for access(). See the FLAG macros and their usage for
|
||||||
|
* examples. All matching flag names are printed with a "|" separator, and if
|
||||||
|
* after evaluating all 'num' entries in 'fp' there are still bits in 'value'
|
||||||
|
* for which nothing has been printed, the remaining bits will be printed with
|
||||||
|
* the 'fmt' format string for an integer (generally "%d" should be used).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_flags(struct trace_proc * proc, const char * name, const struct flags * fp,
|
||||||
|
unsigned int num, const char * fmt, unsigned int value)
|
||||||
|
{
|
||||||
|
unsigned int left;
|
||||||
|
int first;
|
||||||
|
|
||||||
|
if (valuesonly) {
|
||||||
|
put_value(proc, name, fmt, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_field(proc, name, "");
|
||||||
|
|
||||||
|
for (first = TRUE, left = value; num > 0; fp++, num--) {
|
||||||
|
if ((value & fp->mask) == fp->value) {
|
||||||
|
if (first)
|
||||||
|
first = FALSE;
|
||||||
|
else
|
||||||
|
put_text(proc, "|");
|
||||||
|
put_text(proc, fp->name);
|
||||||
|
|
||||||
|
left -= fp->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left != 0) {
|
||||||
|
if (first)
|
||||||
|
first = FALSE;
|
||||||
|
else
|
||||||
|
put_text(proc, "|");
|
||||||
|
|
||||||
|
put_fmt(proc, fmt, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If nothing has been printed so far, simply print a zero. Ignoring
|
||||||
|
* the given format in this case is intentional: a simple 0 looks
|
||||||
|
* better than 0x0 or 00 etc.
|
||||||
|
*/
|
||||||
|
if (first)
|
||||||
|
put_text(proc, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a tail field at the end of an array. The given 'count' value is the
|
||||||
|
* total number of elements in the array, or 0 to indicate that an error
|
||||||
|
* occurred. The given 'printed' value is the number of fields printed so far.
|
||||||
|
* If some fields have been printed already, the number of fields not printed
|
||||||
|
* will be shown as "..(+N)". If no fields have been printed already, the
|
||||||
|
* (total) number of fields not printed will be shown as "..(N)". An error
|
||||||
|
* will print "..(?)".
|
||||||
|
*
|
||||||
|
* The rules for printing an array are as follows. In principle, arrays should
|
||||||
|
* be enclosed in "[]". However, if a copy error occurs immediately, a pointer
|
||||||
|
* to the array should be printed instead. An empty array should be printed as
|
||||||
|
* "[]" (not "[..(0)]"). If a copy error occurs in the middle of the array,
|
||||||
|
* put_tail should be used with count == 0. Only if not all fields in the
|
||||||
|
* array are printed, put_tail should be used with count > 0. The value of
|
||||||
|
* 'printed' is typically the result of an arbitrary limit set based on the
|
||||||
|
* verbosity level.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_tail(struct trace_proc * proc, unsigned int count, unsigned int printed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
put_field(proc, NULL, "..(?)");
|
||||||
|
else
|
||||||
|
put_value(proc, NULL, "..(%s%u)",
|
||||||
|
(printed > 0) ? "+" : "", count - printed);
|
||||||
|
}
|
22
minix/usr.bin/trace/inc.h
Normal file
22
minix/usr.bin/trace/inc.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <minix/config.h>
|
||||||
|
#include <minix/const.h>
|
||||||
|
#include <minix/type.h>
|
||||||
|
#include <minix/ipc.h>
|
||||||
|
#include <minix/com.h>
|
||||||
|
#include <minix/callnr.h>
|
||||||
|
#include <minix/endpoint.h>
|
||||||
|
#include <machine/stackframe.h>
|
||||||
|
|
||||||
|
#include "proc.h"
|
||||||
|
#include "type.h"
|
||||||
|
#include "proto.h"
|
226
minix/usr.bin/trace/ioctl.c
Normal file
226
minix/usr.bin/trace/ioctl.c
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
static char ioctlbuf[IOCPARM_MASK];
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *(*name)(unsigned long);
|
||||||
|
int (*arg)(struct trace_proc *, unsigned long, void *, int);
|
||||||
|
int is_svrctl;
|
||||||
|
} ioctl_table[] = {
|
||||||
|
{ block_ioctl_name, block_ioctl_arg, FALSE },
|
||||||
|
{ char_ioctl_name, char_ioctl_arg, FALSE },
|
||||||
|
{ net_ioctl_name, net_ioctl_arg, FALSE },
|
||||||
|
{ svrctl_name, svrctl_arg, TRUE },
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print an IOCTL request code, and save certain values in the corresponding
|
||||||
|
* process structure in order to be able to print the IOCTL argument.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_ioctl_req(struct trace_proc * proc, const char * name, unsigned long req,
|
||||||
|
int is_svrctl)
|
||||||
|
{
|
||||||
|
const char *text;
|
||||||
|
size_t size;
|
||||||
|
unsigned int group, cmd;
|
||||||
|
int i, r, w, big;
|
||||||
|
|
||||||
|
proc->ioctl_index = -1;
|
||||||
|
|
||||||
|
if (valuesonly > 1) {
|
||||||
|
put_value(proc, name, "0x%lx", req);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lookups are bruteforce across the IOCTL submodules; they're all
|
||||||
|
* checked. We could use the group letter but that would create more
|
||||||
|
* issues than it solves. Our hope is that at least the compiler is
|
||||||
|
* smart about looking up particular codes in each switch statement,
|
||||||
|
* although in the worst case, it's a full O(n) lookup.
|
||||||
|
*/
|
||||||
|
for (i = 0; !valuesonly && i < COUNT(ioctl_table); i++) {
|
||||||
|
/* IOCTLs and SVRCTLs are considered different name spaces. */
|
||||||
|
if (ioctl_table[i].is_svrctl != is_svrctl)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((text = ioctl_table[i].name(req)) != NULL) {
|
||||||
|
put_field(proc, name, text);
|
||||||
|
|
||||||
|
proc->ioctl_index = i;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = _MINIX_IOCTL_IOR(req);
|
||||||
|
w = _MINIX_IOCTL_IOW(req);
|
||||||
|
big = _MINIX_IOCTL_BIG(req);
|
||||||
|
size = (size_t)(big ? _MINIX_IOCTL_SIZE_BIG(req) : IOCPARM_LEN(req));
|
||||||
|
group = big ? 0 : IOCGROUP(req);
|
||||||
|
cmd = req & 0xff; /* shockingly there is no macro for this.. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Not sure why an entire bit is wasted on IOC_VOID (legacy reasons?),
|
||||||
|
* but since the redundancy is there, we might as well check whether
|
||||||
|
* this is a valid IOCTL request. Also, we expect the group to be a
|
||||||
|
* printable character. If either check fails, print just a number.
|
||||||
|
*/
|
||||||
|
if (((req & IOC_VOID) && (r || w || big || size > 0)) ||
|
||||||
|
(!(req & IOC_VOID) && ((!r && !w) || size == 0)) ||
|
||||||
|
(!big && (group < 32 || group > 127))) {
|
||||||
|
put_value(proc, name, "0x%lx", req);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (big) {
|
||||||
|
/* For big IOCTLs, "R" becomes before "W" (old MINIX style). */
|
||||||
|
put_value(proc, name, "_IO%s%s_BIG(%u,%zu)",
|
||||||
|
r ? "R" : "", w ? "W" : "", cmd, size);
|
||||||
|
} else if (IOCGROUP(req) >= 32 && IOCGROUP(req) < 127) {
|
||||||
|
/* For normal IOCTLs, "W" comes before "R" (NetBSD style). */
|
||||||
|
put_value(proc, name, "_IO%s%s('%c',%u,%zu)",
|
||||||
|
w ? "W" : "", r ? "R" : "", group, cmd, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print the supplied (out) part of an IOCTL argument, as applicable. For
|
||||||
|
* efficiency reasons, this function assumes that put_ioctl_req() has been
|
||||||
|
* called for the corresponding IOCTL already, so that the necessary fields in
|
||||||
|
* the given proc structure are set as expected.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
put_ioctl_arg_out(struct trace_proc * proc, const char * name,
|
||||||
|
unsigned long req, vir_bytes addr, int is_svrctl)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
int dir, all;
|
||||||
|
|
||||||
|
dir = (_MINIX_IOCTL_IOW(req) ? IF_OUT : 0) |
|
||||||
|
(_MINIX_IOCTL_IOR(req) ? IF_IN : 0);
|
||||||
|
|
||||||
|
if (dir == 0)
|
||||||
|
proc->ioctl_index = -1; /* no argument to print at all */
|
||||||
|
|
||||||
|
/* No support for printing big-IOCTL contents just yet. */
|
||||||
|
if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
|
||||||
|
proc->ioctl_index == -1) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(proc->ioctl_index >= 0);
|
||||||
|
assert(proc->ioctl_index < COUNT(ioctl_table));
|
||||||
|
assert(ioctl_table[proc->ioctl_index].is_svrctl == is_svrctl);
|
||||||
|
|
||||||
|
proc->ioctl_flags =
|
||||||
|
ioctl_table[proc->ioctl_index].arg(proc, req, NULL, dir);
|
||||||
|
|
||||||
|
if (proc->ioctl_flags == 0) { /* no argument printing for this IOCTL */
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
proc->ioctl_index = -1; /* forget about the IOCTL handler */
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this triggers, the IOCTL handler returns a direction that is not
|
||||||
|
* part of the actual IOCTL, and the handler should be fixed.
|
||||||
|
*/
|
||||||
|
if (proc->ioctl_flags & ~dir) {
|
||||||
|
output_flush(); /* show the IOCTL name for debugging */
|
||||||
|
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(proc->ioctl_flags & IF_OUT))
|
||||||
|
return CT_NOTDONE;
|
||||||
|
|
||||||
|
size = IOCPARM_LEN(req);
|
||||||
|
|
||||||
|
if (size > sizeof(ioctlbuf) ||
|
||||||
|
mem_get_data(proc->pid, addr, ioctlbuf, size) < 0) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
/* There's no harm in trying the _in side later anyhow.. */
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_open(proc, name, 0, "{", ", ");
|
||||||
|
|
||||||
|
all = ioctl_table[proc->ioctl_index].arg(proc, req, ioctlbuf, IF_OUT);
|
||||||
|
|
||||||
|
if (!all)
|
||||||
|
put_field(proc, NULL, "..");
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print the returned (in) part of an IOCTL argument, as applicable. This
|
||||||
|
* function assumes that it is preceded by a call to put_ioctl_arg_out for this
|
||||||
|
* process.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_ioctl_arg_in(struct trace_proc * proc, const char * name, int failed,
|
||||||
|
unsigned long req, vir_bytes addr, int is_svrctl)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
int all;
|
||||||
|
|
||||||
|
if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
|
||||||
|
proc->ioctl_index == -1) {
|
||||||
|
put_result(proc);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(proc->ioctl_index >= 0);
|
||||||
|
assert(proc->ioctl_index < COUNT(ioctl_table));
|
||||||
|
assert(ioctl_table[proc->ioctl_index].is_svrctl == is_svrctl);
|
||||||
|
assert(proc->ioctl_flags != 0);
|
||||||
|
|
||||||
|
if (proc->ioctl_flags & IF_OUT)
|
||||||
|
put_result(proc);
|
||||||
|
if (!(proc->ioctl_flags & IF_IN))
|
||||||
|
return;
|
||||||
|
|
||||||
|
size = IOCPARM_LEN(req);
|
||||||
|
|
||||||
|
if (failed || size > sizeof(ioctlbuf) ||
|
||||||
|
mem_get_data(proc->pid, addr, ioctlbuf, size) < 0) {
|
||||||
|
if (!(proc->ioctl_flags & IF_OUT)) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
put_equals(proc);
|
||||||
|
put_result(proc);
|
||||||
|
} else if (!failed)
|
||||||
|
put_field(proc, NULL, "{..}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_open(proc, name, 0, "{", ", ");
|
||||||
|
|
||||||
|
all = ioctl_table[proc->ioctl_index].arg(proc, req, ioctlbuf, IF_IN);
|
||||||
|
|
||||||
|
if (!all)
|
||||||
|
put_field(proc, NULL, "..");
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
|
||||||
|
if (!(proc->ioctl_flags & IF_OUT)) {
|
||||||
|
put_equals(proc);
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
}
|
229
minix/usr.bin/trace/ioctl/block.c
Normal file
229
minix/usr.bin/trace/ioctl/block.c
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <minix/partition.h>
|
||||||
|
#include <sys/vm.h>
|
||||||
|
#include <sys/mtio.h>
|
||||||
|
|
||||||
|
const char *
|
||||||
|
block_ioctl_name(unsigned long req)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
NAME(BIOCTRACEBUF);
|
||||||
|
NAME(BIOCTRACECTL);
|
||||||
|
NAME(BIOCTRACEGET); /* big IOCTL, not printing argument */
|
||||||
|
NAME(DIOCSETP);
|
||||||
|
NAME(DIOCGETP);
|
||||||
|
NAME(DIOCEJECT); /* no argument */
|
||||||
|
NAME(DIOCTIMEOUT);
|
||||||
|
NAME(DIOCOPENCT);
|
||||||
|
NAME(DIOCFLUSH); /* no argument */
|
||||||
|
NAME(DIOCGETWC);
|
||||||
|
NAME(DIOCSETWC);
|
||||||
|
NAME(FBDCADDRULE);
|
||||||
|
NAME(FBDCDELRULE);
|
||||||
|
NAME(FBDCGETRULE);
|
||||||
|
NAME(MIOCRAMSIZE);
|
||||||
|
NAME(MTIOCGET); /* TODO: print argument */
|
||||||
|
NAME(MTIOCTOP); /* TODO: print argument */
|
||||||
|
NAME(VNDIOCCLR);
|
||||||
|
NAME(VNDIOCGET);
|
||||||
|
NAME(VNDIOCSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags fbd_flags[] = {
|
||||||
|
FLAG(FBD_FLAG_READ),
|
||||||
|
FLAG(FBD_FLAG_WRITE),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_fbd_action(struct trace_proc * proc, const char * name, int action)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (action) {
|
||||||
|
TEXT(FBD_ACTION_CORRUPT);
|
||||||
|
TEXT(FBD_ACTION_ERROR);
|
||||||
|
TEXT(FBD_ACTION_MISDIR);
|
||||||
|
TEXT(FBD_ACTION_LOSTTORN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags vnd_flags[] = {
|
||||||
|
FLAG(VNDIOF_HASGEOM),
|
||||||
|
FLAG(VNDIOF_READONLY),
|
||||||
|
FLAG(VNDIOF_FORCE),
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
block_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr,
|
||||||
|
int dir)
|
||||||
|
{
|
||||||
|
struct part_geom *part;
|
||||||
|
struct fbd_rule *rule;
|
||||||
|
struct vnd_ioctl *vnd;
|
||||||
|
struct vnd_user *vnu;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
case BIOCTRACEBUF:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%zu", *(size_t *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case BIOCTRACECTL:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
i = *(int *)ptr;
|
||||||
|
if (!valuesonly && i == BTCTL_START)
|
||||||
|
put_field(proc, NULL, "BTCTL_START");
|
||||||
|
else if (!valuesonly && i == BTCTL_STOP)
|
||||||
|
put_field(proc, NULL, "BTCTL_STOP");
|
||||||
|
else
|
||||||
|
put_value(proc, NULL, "%d", i);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case DIOCSETP:
|
||||||
|
if ((part = (struct part_geom *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, "base", "%"PRIu64, part->base);
|
||||||
|
put_value(proc, "size", "%"PRIu64, part->size);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case DIOCGETP:
|
||||||
|
if ((part = (struct part_geom *)ptr) == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_value(proc, "base", "%"PRIu64, part->base);
|
||||||
|
put_value(proc, "size", "%"PRIu64, part->size);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "cylinders", "%u", part->cylinders);
|
||||||
|
put_value(proc, "heads", "%u", part->heads);
|
||||||
|
put_value(proc, "sectors", "%u", part->sectors);
|
||||||
|
return IF_ALL;
|
||||||
|
} else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case DIOCTIMEOUT:
|
||||||
|
/* Print the old timeout only if verbosity is high enough. */
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT | ((verbose > 0) ? IF_IN : 0);
|
||||||
|
|
||||||
|
/* Same action for out and in. */
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case DIOCOPENCT:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case DIOCSETWC:
|
||||||
|
case DIOCGETWC:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir; /* out or in, depending on the request */
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case FBDCDELRULE:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(fbd_rulenum_t *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case FBDCGETRULE:
|
||||||
|
if ((rule = (struct fbd_rule *)ptr) == NULL)
|
||||||
|
return IF_OUT | IF_IN;
|
||||||
|
|
||||||
|
if (dir == IF_OUT) {
|
||||||
|
put_value(proc, "num", "%d", rule->num);
|
||||||
|
return IF_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The returned result is the same as what is passed to the
|
||||||
|
* add request, so we can use the same code to print both.
|
||||||
|
*/
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
case FBDCADDRULE:
|
||||||
|
if ((rule = (struct fbd_rule *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
if (rule->start != 0 || rule->end != 0 || verbose > 0) {
|
||||||
|
put_value(proc, "start", "%"PRIu64, rule->start);
|
||||||
|
put_value(proc, "end", "%"PRIu64, rule->end);
|
||||||
|
}
|
||||||
|
if (rule->flags != (FBD_FLAG_READ | FBD_FLAG_WRITE) ||
|
||||||
|
verbose > 0)
|
||||||
|
put_flags(proc, "flags", fbd_flags, COUNT(fbd_flags),
|
||||||
|
"0x%x", rule->flags);
|
||||||
|
if (rule->skip != 0 || verbose > 0)
|
||||||
|
put_value(proc, "skip", "%u", rule->skip);
|
||||||
|
if (rule->count != 0 || verbose > 0)
|
||||||
|
put_value(proc, "count", "%u", rule->count);
|
||||||
|
put_fbd_action(proc, "action", rule->action);
|
||||||
|
|
||||||
|
return 0; /* TODO: optionally print the union fields */
|
||||||
|
|
||||||
|
case MIOCRAMSIZE:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%"PRIu32, *(u32_t *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case VNDIOCSET:
|
||||||
|
if ((vnd = (struct vnd_ioctl *)ptr) == NULL)
|
||||||
|
return IF_OUT | IF_IN;
|
||||||
|
|
||||||
|
if (dir == IF_OUT) {
|
||||||
|
put_value(proc, "vnd_fildes", "%d", vnd->vnd_fildes);
|
||||||
|
put_flags(proc, "vnd_flags", vnd_flags,
|
||||||
|
COUNT(vnd_flags), "0x%x", vnd->vnd_flags);
|
||||||
|
return 0; /* TODO: print geometry if given */
|
||||||
|
} else {
|
||||||
|
put_value(proc, "vnd_size", "%"PRIu64, vnd->vnd_size);
|
||||||
|
return IF_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
case VNDIOCCLR:
|
||||||
|
if ((vnd = (struct vnd_ioctl *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_flags(proc, "vnd_flags", vnd_flags, COUNT(vnd_flags),
|
||||||
|
"0x%x", vnd->vnd_flags);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case VNDIOCGET:
|
||||||
|
if ((vnu = (struct vnd_user *)ptr) == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_value(proc, "vnu_unit", "%d", vnu->vnu_unit);
|
||||||
|
put_dev(proc, "vnu_dev", vnu->vnu_dev);
|
||||||
|
put_value(proc, "vnu_ino", "%"PRId64, vnu->vnu_ino);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
509
minix/usr.bin/trace/ioctl/char.c
Normal file
509
minix/usr.bin/trace/ioctl/char.c
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <minix/i2c.h>
|
||||||
|
#include <minix/fb.h>
|
||||||
|
#include <minix/sound.h>
|
||||||
|
#include <sys/termios.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/kbdio.h>
|
||||||
|
#include <minix/keymap.h>
|
||||||
|
#include <sys/vm.h>
|
||||||
|
#include <sys/fcntl.h>
|
||||||
|
|
||||||
|
const char *
|
||||||
|
char_ioctl_name(unsigned long req)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
NAME(MINIX_I2C_IOCTL_EXEC);
|
||||||
|
NAME(FBIOGET_VSCREENINFO);
|
||||||
|
NAME(FBIOPUT_VSCREENINFO);
|
||||||
|
NAME(FBIOGET_FSCREENINFO); /* TODO: print argument */
|
||||||
|
NAME(FBIOPAN_DISPLAY);
|
||||||
|
NAME(DSPIORATE);
|
||||||
|
NAME(DSPIOSTEREO);
|
||||||
|
NAME(DSPIOSIZE);
|
||||||
|
NAME(DSPIOBITS);
|
||||||
|
NAME(DSPIOSIGN);
|
||||||
|
NAME(DSPIOMAX);
|
||||||
|
NAME(DSPIORESET); /* no argument */
|
||||||
|
NAME(DSPIOFREEBUF);
|
||||||
|
NAME(DSPIOSAMPLESINBUF);
|
||||||
|
NAME(DSPIOPAUSE); /* no argument */
|
||||||
|
NAME(DSPIORESUME); /* no argument */
|
||||||
|
NAME(MIXIOGETVOLUME);
|
||||||
|
NAME(MIXIOGETINPUTLEFT);
|
||||||
|
NAME(MIXIOGETINPUTRIGHT);
|
||||||
|
NAME(MIXIOGETOUTPUT);
|
||||||
|
NAME(MIXIOSETVOLUME);
|
||||||
|
NAME(MIXIOSETINPUTLEFT);
|
||||||
|
NAME(MIXIOSETINPUTRIGHT);
|
||||||
|
NAME(MIXIOSETOUTPUT);
|
||||||
|
NAME(TIOCEXCL); /* no argument */
|
||||||
|
NAME(TIOCNXCL); /* no argument */
|
||||||
|
NAME(TIOCFLUSH);
|
||||||
|
NAME(TIOCGETA);
|
||||||
|
NAME(TIOCSETA);
|
||||||
|
NAME(TIOCSETAW);
|
||||||
|
NAME(TIOCSETAF);
|
||||||
|
NAME(TIOCGETD);
|
||||||
|
NAME(TIOCSETD);
|
||||||
|
NAME(TIOCGLINED);
|
||||||
|
NAME(TIOCSLINED);
|
||||||
|
NAME(TIOCSBRK); /* no argument */
|
||||||
|
NAME(TIOCCBRK); /* no argument */
|
||||||
|
NAME(TIOCSDTR); /* no argument */
|
||||||
|
NAME(TIOCCDTR); /* no argument */
|
||||||
|
NAME(TIOCGPGRP);
|
||||||
|
NAME(TIOCSPGRP);
|
||||||
|
NAME(TIOCOUTQ);
|
||||||
|
NAME(TIOCSTI);
|
||||||
|
NAME(TIOCNOTTY); /* no argument */
|
||||||
|
NAME(TIOCPKT);
|
||||||
|
NAME(TIOCSTOP); /* no argument */
|
||||||
|
NAME(TIOCSTART); /* no argument */
|
||||||
|
NAME(TIOCMSET); /* TODO: print argument */
|
||||||
|
NAME(TIOCMBIS); /* TODO: print argument */
|
||||||
|
NAME(TIOCMBIC); /* TODO: print argument */
|
||||||
|
NAME(TIOCMGET); /* TODO: print argument */
|
||||||
|
NAME(TIOCREMOTE);
|
||||||
|
NAME(TIOCGWINSZ);
|
||||||
|
NAME(TIOCSWINSZ);
|
||||||
|
NAME(TIOCUCNTL);
|
||||||
|
NAME(TIOCSTAT);
|
||||||
|
NAME(TIOCGSID);
|
||||||
|
NAME(TIOCCONS);
|
||||||
|
NAME(TIOCSCTTY); /* no argument */
|
||||||
|
NAME(TIOCEXT);
|
||||||
|
NAME(TIOCSIG); /* no argument */
|
||||||
|
NAME(TIOCDRAIN); /* no argument */
|
||||||
|
NAME(TIOCGFLAGS); /* TODO: print argument */
|
||||||
|
NAME(TIOCSFLAGS); /* TODO: print argument */
|
||||||
|
NAME(TIOCDCDTIMESTAMP); /* TODO: print argument */
|
||||||
|
NAME(TIOCRCVFRAME); /* TODO: print argument */
|
||||||
|
NAME(TIOCXMTFRAME); /* TODO: print argument */
|
||||||
|
NAME(TIOCPTMGET); /* TODO: print argument */
|
||||||
|
NAME(TIOCGRANTPT); /* no argument */
|
||||||
|
NAME(TIOCPTSNAME); /* TODO: print argument */
|
||||||
|
NAME(TIOCSQSIZE);
|
||||||
|
NAME(TIOCGQSIZE);
|
||||||
|
NAME(TIOCSFON); /* big IOCTL, not printing argument */
|
||||||
|
NAME(KIOCBELL);
|
||||||
|
NAME(KIOCSLEDS);
|
||||||
|
NAME(KIOCSMAP); /* not worth interpreting */
|
||||||
|
NAME(TIOCMAPMEM);
|
||||||
|
NAME(TIOCUNMAPMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_i2c_op(struct trace_proc * proc, const char *name, i2c_op_t op)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (op) {
|
||||||
|
TEXT(I2C_OP_READ);
|
||||||
|
TEXT(I2C_OP_READ_WITH_STOP);
|
||||||
|
TEXT(I2C_OP_WRITE);
|
||||||
|
TEXT(I2C_OP_WRITE_WITH_STOP);
|
||||||
|
TEXT(I2C_OP_READ_BLOCK);
|
||||||
|
TEXT(I2C_OP_WRITE_BLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_sound_device(struct trace_proc * proc, const char * name, int device)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (device) {
|
||||||
|
TEXT(Master);
|
||||||
|
TEXT(Dac);
|
||||||
|
TEXT(Fm);
|
||||||
|
TEXT(Cd);
|
||||||
|
TEXT(Line);
|
||||||
|
TEXT(Mic);
|
||||||
|
TEXT(Speaker);
|
||||||
|
TEXT(Treble);
|
||||||
|
TEXT(Bass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", device);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_sound_state(struct trace_proc * proc, const char * name, int state)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!valuesonly && state == ON)
|
||||||
|
put_field(proc, name, "ON");
|
||||||
|
else if (!valuesonly && state == OFF)
|
||||||
|
put_field(proc, name, "OFF");
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags flush_flags[] = {
|
||||||
|
FLAG(FREAD),
|
||||||
|
FLAG(FWRITE),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags tc_iflags[] = {
|
||||||
|
FLAG(IGNBRK),
|
||||||
|
FLAG(BRKINT),
|
||||||
|
FLAG(IGNPAR),
|
||||||
|
FLAG(PARMRK),
|
||||||
|
FLAG(INPCK),
|
||||||
|
FLAG(ISTRIP),
|
||||||
|
FLAG(INLCR),
|
||||||
|
FLAG(IGNCR),
|
||||||
|
FLAG(ICRNL),
|
||||||
|
FLAG(IXON),
|
||||||
|
FLAG(IXOFF),
|
||||||
|
FLAG(IXANY),
|
||||||
|
FLAG(IMAXBEL),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags tc_oflags[] = {
|
||||||
|
FLAG(OPOST),
|
||||||
|
FLAG(ONLCR),
|
||||||
|
FLAG(OXTABS),
|
||||||
|
FLAG(ONOEOT),
|
||||||
|
FLAG(OCRNL),
|
||||||
|
FLAG(ONOCR),
|
||||||
|
FLAG(ONLRET),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags tc_cflags[] = {
|
||||||
|
FLAG(CIGNORE),
|
||||||
|
FLAG_MASK(CSIZE, CS5),
|
||||||
|
FLAG_MASK(CSIZE, CS6),
|
||||||
|
FLAG_MASK(CSIZE, CS7),
|
||||||
|
FLAG_MASK(CSIZE, CS8),
|
||||||
|
FLAG(CSTOPB),
|
||||||
|
FLAG(CREAD),
|
||||||
|
FLAG(PARENB),
|
||||||
|
FLAG(PARODD),
|
||||||
|
FLAG(HUPCL),
|
||||||
|
FLAG(CLOCAL),
|
||||||
|
FLAG(CRTSCTS),
|
||||||
|
FLAG(CDTRCTS),
|
||||||
|
FLAG(MDMBUF),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags tc_lflags[] = {
|
||||||
|
FLAG(ECHOKE),
|
||||||
|
FLAG(ECHOE),
|
||||||
|
FLAG(ECHOK),
|
||||||
|
FLAG(ECHO),
|
||||||
|
FLAG(ECHONL),
|
||||||
|
FLAG(ECHOPRT),
|
||||||
|
FLAG(ECHOCTL),
|
||||||
|
FLAG(ISIG),
|
||||||
|
FLAG(ICANON),
|
||||||
|
FLAG(ALTWERASE),
|
||||||
|
FLAG(IEXTEN),
|
||||||
|
FLAG(EXTPROC),
|
||||||
|
FLAG(TOSTOP),
|
||||||
|
FLAG(FLUSHO),
|
||||||
|
FLAG(NOKERNINFO),
|
||||||
|
FLAG(PENDIN),
|
||||||
|
FLAG(NOFLSH),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_tty_disc(struct trace_proc * proc, const char * name, int disc)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (disc) {
|
||||||
|
TEXT(TTYDISC);
|
||||||
|
TEXT(TABLDISC);
|
||||||
|
TEXT(SLIPDISC);
|
||||||
|
TEXT(PPPDISC);
|
||||||
|
TEXT(STRIPDISC);
|
||||||
|
TEXT(HDLCDISC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", disc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags kbd_leds[] = {
|
||||||
|
FLAG(KBD_LEDS_NUM),
|
||||||
|
FLAG(KBD_LEDS_CAPS),
|
||||||
|
FLAG(KBD_LEDS_SCROLL),
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr,
|
||||||
|
int dir)
|
||||||
|
{
|
||||||
|
minix_i2c_ioctl_exec_t *iie;
|
||||||
|
struct fb_var_screeninfo *fbvs;
|
||||||
|
struct volume_level *level;
|
||||||
|
struct inout_ctrl *inout;
|
||||||
|
struct termios *tc;
|
||||||
|
struct winsize *ws;
|
||||||
|
struct kio_bell *bell;
|
||||||
|
struct kio_leds *leds;
|
||||||
|
struct mapreqvm *mapreq;
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
case MINIX_I2C_IOCTL_EXEC:
|
||||||
|
if ((iie = (minix_i2c_ioctl_exec_t *)ptr) == NULL)
|
||||||
|
return IF_OUT; /* we print only the request for now */
|
||||||
|
|
||||||
|
put_i2c_op(proc, "iie_op", iie->iie_op);
|
||||||
|
put_value(proc, "iie_addr", "0x%04x", iie->iie_addr);
|
||||||
|
return 0; /* TODO: print command/data/result */
|
||||||
|
|
||||||
|
case FBIOGET_VSCREENINFO:
|
||||||
|
if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_value(proc, "xres", "%"PRIu32, fbvs->xres);
|
||||||
|
put_value(proc, "yres", "%"PRIu32, fbvs->yres);
|
||||||
|
put_value(proc, "xres_virtual", "%"PRIu32, fbvs->xres_virtual);
|
||||||
|
put_value(proc, "yres_virtual", "%"PRIu32, fbvs->yres_virtual);
|
||||||
|
put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
|
||||||
|
put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
|
||||||
|
put_value(proc, "bits_per_pixel", "%"PRIu32,
|
||||||
|
fbvs->bits_per_pixel);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case FBIOPUT_VSCREENINFO:
|
||||||
|
case FBIOPAN_DISPLAY:
|
||||||
|
if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
|
||||||
|
put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case DSPIORATE:
|
||||||
|
case DSPIOSTEREO:
|
||||||
|
case DSPIOSIZE:
|
||||||
|
case DSPIOBITS:
|
||||||
|
case DSPIOSIGN:
|
||||||
|
case DSPIOMAX:
|
||||||
|
case DSPIOFREEBUF:
|
||||||
|
case DSPIOSAMPLESINBUF:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%u", *(unsigned int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case MIXIOGETVOLUME:
|
||||||
|
if ((level = (struct volume_level *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
if (dir == IF_OUT)
|
||||||
|
put_sound_device(proc, "device", level->device);
|
||||||
|
else {
|
||||||
|
put_value(proc, "left", "%d", level->left);
|
||||||
|
put_value(proc, "right", "%d", level->right);
|
||||||
|
}
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case MIXIOSETVOLUME:
|
||||||
|
/* Print the corrected volume levels only with verbosity on. */
|
||||||
|
if ((level = (struct volume_level *)ptr) == NULL)
|
||||||
|
return IF_OUT | ((verbose > 0) ? IF_IN : 0);
|
||||||
|
|
||||||
|
if (dir == IF_OUT)
|
||||||
|
put_sound_device(proc, "device", level->device);
|
||||||
|
put_value(proc, "left", "%d", level->left);
|
||||||
|
put_value(proc, "right", "%d", level->right);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case MIXIOGETINPUTLEFT:
|
||||||
|
case MIXIOGETINPUTRIGHT:
|
||||||
|
case MIXIOGETOUTPUT:
|
||||||
|
if ((inout = (struct inout_ctrl *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
if (dir == IF_OUT)
|
||||||
|
put_sound_device(proc, "device", inout->device);
|
||||||
|
else {
|
||||||
|
put_sound_state(proc, "left", inout->left);
|
||||||
|
put_sound_state(proc, "right", inout->right);
|
||||||
|
}
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case MIXIOSETINPUTLEFT:
|
||||||
|
case MIXIOSETINPUTRIGHT:
|
||||||
|
case MIXIOSETOUTPUT:
|
||||||
|
if ((inout = (struct inout_ctrl *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_sound_device(proc, "device", inout->device);
|
||||||
|
put_sound_state(proc, "left", inout->left);
|
||||||
|
put_sound_state(proc, "right", inout->right);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCFLUSH:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_flags(proc, NULL, flush_flags, COUNT(flush_flags), "0x%x",
|
||||||
|
*(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCGETA:
|
||||||
|
case TIOCSETA:
|
||||||
|
case TIOCSETAW:
|
||||||
|
case TIOCSETAF:
|
||||||
|
if ((tc = (struct termios *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are fairly common IOCTLs, so printing everything by
|
||||||
|
* default would create a lot of noise. By default we limit
|
||||||
|
* ourselves to printing the field that contains what I
|
||||||
|
* consider to be the most important flag: ICANON.
|
||||||
|
* TODO: see if we can come up with a decent format for
|
||||||
|
* selectively printing (relatively important) flags.
|
||||||
|
*/
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_flags(proc, "c_iflag", tc_iflags, COUNT(tc_iflags),
|
||||||
|
"0x%x", tc->c_iflag);
|
||||||
|
put_flags(proc, "c_oflag", tc_oflags, COUNT(tc_oflags),
|
||||||
|
"0x%x", tc->c_oflag);
|
||||||
|
put_flags(proc, "c_cflag", tc_cflags, COUNT(tc_cflags),
|
||||||
|
"0x%x", tc->c_cflag);
|
||||||
|
}
|
||||||
|
put_flags(proc, "c_lflag", tc_lflags, COUNT(tc_lflags), "0x%x",
|
||||||
|
tc->c_lflag);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "c_ispeed", "%d", tc->c_ispeed);
|
||||||
|
put_value(proc, "c_ospeed", "%d", tc->c_ospeed);
|
||||||
|
}
|
||||||
|
return 0; /* TODO: print the c_cc fields */
|
||||||
|
|
||||||
|
case TIOCGETD:
|
||||||
|
case TIOCSETD:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_tty_disc(proc, NULL, *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCGLINED:
|
||||||
|
case TIOCSLINED:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_buf(proc, NULL, PF_LOCADDR | PF_STRING, (vir_bytes)ptr,
|
||||||
|
sizeof(linedn_t));
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCGPGRP:
|
||||||
|
case TIOCSPGRP:
|
||||||
|
case TIOCOUTQ:
|
||||||
|
case TIOCPKT:
|
||||||
|
case TIOCREMOTE:
|
||||||
|
case TIOCUCNTL:
|
||||||
|
case TIOCSTAT: /* argument seems unused? */
|
||||||
|
case TIOCGSID:
|
||||||
|
case TIOCCONS: /* argument seems unused? */
|
||||||
|
case TIOCEXT:
|
||||||
|
case TIOCSQSIZE:
|
||||||
|
case TIOCGQSIZE:
|
||||||
|
/* Print a simple integer. */
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCSTI:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
if (!valuesonly)
|
||||||
|
put_value(proc, NULL, "'%s'",
|
||||||
|
get_escape(*(char *)ptr));
|
||||||
|
else
|
||||||
|
put_value(proc, NULL, "%u", *(char *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCGWINSZ:
|
||||||
|
case TIOCSWINSZ:
|
||||||
|
if ((ws = (struct winsize *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
/* This is a stupid order, but we follow the struct layout. */
|
||||||
|
put_value(proc, "ws_row", "%u", ws->ws_row);
|
||||||
|
put_value(proc, "ws_col", "%u", ws->ws_col);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "ws_xpixel", "%u", ws->ws_xpixel);
|
||||||
|
put_value(proc, "ws_ypixel", "%u", ws->ws_ypixel);
|
||||||
|
}
|
||||||
|
return (verbose > 0) ? IF_ALL : 0;
|
||||||
|
|
||||||
|
case KIOCBELL:
|
||||||
|
if ((bell = (struct kio_bell *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, "kb_pitch", "%u", bell->kb_pitch);
|
||||||
|
put_value(proc, "kb_volume", "%lu", bell->kb_volume);
|
||||||
|
put_struct_timeval(proc, "kb_duration", PF_LOCADDR,
|
||||||
|
(vir_bytes)&bell->kb_duration);
|
||||||
|
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case KIOCSLEDS:
|
||||||
|
if ((leds = (struct kio_leds *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_flags(proc, "kl_bits", kbd_leds, COUNT(kbd_leds), "0x%x",
|
||||||
|
leds->kl_bits);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCMAPMEM:
|
||||||
|
if ((mapreq = (struct mapreqvm *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
/* This structure has more fields, but they're all unused.. */
|
||||||
|
if (dir == IF_OUT) {
|
||||||
|
put_value(proc, "phys_offset", "%"PRIu64,
|
||||||
|
(uint64_t)mapreq->phys_offset); /* future compat */
|
||||||
|
put_value(proc, "size", "%zu", mapreq->size);
|
||||||
|
} else
|
||||||
|
put_ptr(proc, "vaddr_ret", (vir_bytes)mapreq->vaddr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case TIOCUNMAPMEM:
|
||||||
|
if ((mapreq = (struct mapreqvm *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_ptr(proc, "vaddr", (vir_bytes)mapreq->vaddr);
|
||||||
|
put_value(proc, "size", "%zu", mapreq->size);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
565
minix/usr.bin/trace/ioctl/net.c
Normal file
565
minix/usr.bin/trace/ioctl/net.c
Normal file
|
@ -0,0 +1,565 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/ucred.h>
|
||||||
|
#include <net/gen/in.h>
|
||||||
|
#include <net/gen/ether.h>
|
||||||
|
#include <net/gen/eth_io.h>
|
||||||
|
#include <net/gen/arp_io.h>
|
||||||
|
#include <net/gen/ip_io.h>
|
||||||
|
#include <net/gen/route.h>
|
||||||
|
#include <net/gen/tcp.h>
|
||||||
|
#include <net/gen/tcp_io.h>
|
||||||
|
#include <net/gen/udp.h>
|
||||||
|
#include <net/gen/udp_io.h>
|
||||||
|
#include <net/gen/udp_io_hdr.h>
|
||||||
|
#include <net/gen/psip_io.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
const char *
|
||||||
|
net_ioctl_name(unsigned long req)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
NAME(FIONREAD);
|
||||||
|
NAME(NWIOSETHOPT); /* TODO: print argument */
|
||||||
|
NAME(NWIOGETHOPT); /* TODO: print argument */
|
||||||
|
NAME(NWIOGETHSTAT); /* TODO: print argument */
|
||||||
|
NAME(NWIOARPGIP); /* TODO: print argument */
|
||||||
|
NAME(NWIOARPGNEXT); /* TODO: print argument */
|
||||||
|
NAME(NWIOARPSIP); /* TODO: print argument */
|
||||||
|
NAME(NWIOARPDIP); /* TODO: print argument */
|
||||||
|
NAME(NWIOSIPCONF2); /* TODO: print argument */
|
||||||
|
NAME(NWIOSIPCONF); /* TODO: print argument */
|
||||||
|
NAME(NWIOGIPCONF2); /* TODO: print argument */
|
||||||
|
NAME(NWIOGIPCONF); /* TODO: print argument */
|
||||||
|
NAME(NWIOSIPOPT);
|
||||||
|
NAME(NWIOGIPOPT);
|
||||||
|
NAME(NWIOGIPOROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIOSIPOROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIODIPOROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIOGIPIROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIOSIPIROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIODIPIROUTE); /* TODO: print argument */
|
||||||
|
NAME(NWIOSTCPCONF);
|
||||||
|
NAME(NWIOGTCPCONF);
|
||||||
|
NAME(NWIOTCPCONN);
|
||||||
|
NAME(NWIOTCPLISTEN);
|
||||||
|
NAME(NWIOTCPATTACH); /* TODO: print argument */
|
||||||
|
NAME(NWIOTCPSHUTDOWN); /* no argument */
|
||||||
|
NAME(NWIOSTCPOPT);
|
||||||
|
NAME(NWIOGTCPOPT);
|
||||||
|
NAME(NWIOTCPPUSH); /* no argument */
|
||||||
|
NAME(NWIOTCPLISTENQ);
|
||||||
|
NAME(NWIOGTCPCOOKIE);
|
||||||
|
NAME(NWIOTCPACCEPTTO);
|
||||||
|
NAME(NWIOTCPGERROR);
|
||||||
|
NAME(NWIOSUDPOPT);
|
||||||
|
NAME(NWIOGUDPOPT);
|
||||||
|
NAME(NWIOUDPPEEK); /* TODO: print argument */
|
||||||
|
NAME(NWIOSPSIPOPT); /* TODO: print argument */
|
||||||
|
NAME(NWIOGPSIPOPT); /* TODO: print argument */
|
||||||
|
NAME(NWIOGUDSFADDR);
|
||||||
|
NAME(NWIOSUDSTADDR);
|
||||||
|
NAME(NWIOSUDSADDR);
|
||||||
|
NAME(NWIOGUDSADDR);
|
||||||
|
NAME(NWIOGUDSPADDR);
|
||||||
|
NAME(NWIOSUDSTYPE);
|
||||||
|
NAME(NWIOSUDSBLOG);
|
||||||
|
NAME(NWIOSUDSCONN);
|
||||||
|
NAME(NWIOSUDSSHUT);
|
||||||
|
NAME(NWIOSUDSPAIR);
|
||||||
|
NAME(NWIOSUDSACCEPT);
|
||||||
|
NAME(NWIOSUDSCTRL);
|
||||||
|
NAME(NWIOGUDSCTRL);
|
||||||
|
NAME(NWIOGUDSSOTYPE);
|
||||||
|
NAME(NWIOGUDSPEERCRED);
|
||||||
|
NAME(NWIOGUDSSNDBUF);
|
||||||
|
NAME(NWIOSUDSSNDBUF);
|
||||||
|
NAME(NWIOGUDSRCVBUF);
|
||||||
|
NAME(NWIOSUDSRCVBUF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags ipopt_flags[] = {
|
||||||
|
FLAG_ZERO(NWIO_NOFLAGS),
|
||||||
|
FLAG_MASK(NWIO_ACC_MASK, NWIO_EXCL),
|
||||||
|
FLAG_MASK(NWIO_ACC_MASK, NWIO_SHARED),
|
||||||
|
FLAG_MASK(NWIO_ACC_MASK, NWIO_COPY),
|
||||||
|
FLAG(NWIO_EN_LOC),
|
||||||
|
FLAG(NWIO_DI_LOC),
|
||||||
|
FLAG(NWIO_EN_BROAD),
|
||||||
|
FLAG(NWIO_DI_BROAD),
|
||||||
|
FLAG(NWIO_REMSPEC),
|
||||||
|
FLAG(NWIO_REMANY),
|
||||||
|
FLAG(NWIO_PROTOSPEC),
|
||||||
|
FLAG(NWIO_PROTOANY),
|
||||||
|
FLAG(NWIO_HDR_O_SPEC),
|
||||||
|
FLAG(NWIO_HDR_O_ANY),
|
||||||
|
FLAG(NWIO_RWDATONLY),
|
||||||
|
FLAG(NWIO_RWDATALL),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_ipaddr(struct trace_proc * proc, const char * name, ipaddr_t ipaddr)
|
||||||
|
{
|
||||||
|
struct in_addr in;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
in.s_addr = ipaddr;
|
||||||
|
|
||||||
|
/* Is this an acceptable encapsulation? */
|
||||||
|
put_value(proc, name, "[%s]", inet_ntoa(in));
|
||||||
|
} else
|
||||||
|
put_value(proc, name, "0x%08x", ntohl(ipaddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_ipproto(struct trace_proc * proc, const char * name, ipproto_t proto)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (proto) {
|
||||||
|
TEXT(IPPROTO_ICMP);
|
||||||
|
TEXT(IPPROTO_TCP);
|
||||||
|
TEXT(IPPROTO_UDP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%u", proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags tcpconf_flags[] = {
|
||||||
|
FLAG_ZERO(NWTC_NOFLAGS),
|
||||||
|
FLAG_MASK(NWTC_ACC_MASK, NWTC_EXCL),
|
||||||
|
FLAG_MASK(NWTC_ACC_MASK, NWTC_SHARED),
|
||||||
|
FLAG_MASK(NWTC_ACC_MASK, NWTC_COPY),
|
||||||
|
FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_UNSET),
|
||||||
|
FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_SET),
|
||||||
|
FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_SEL),
|
||||||
|
FLAG(NWTC_SET_RA),
|
||||||
|
FLAG(NWTC_UNSET_RA),
|
||||||
|
FLAG(NWTC_SET_RP),
|
||||||
|
FLAG(NWTC_UNSET_RP),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define put_port(proc, name, port) \
|
||||||
|
put_value(proc, name, "%u", ntohs(port))
|
||||||
|
|
||||||
|
static const struct flags tcpcl_flags[] = {
|
||||||
|
FLAG_ZERO(TCF_DEFAULT),
|
||||||
|
FLAG(TCF_ASYNCH),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags tcpopt_flags[] = {
|
||||||
|
FLAG_ZERO(NWTO_NOFLAG),
|
||||||
|
FLAG(NWTO_SND_URG),
|
||||||
|
FLAG(NWTO_SND_NOTURG),
|
||||||
|
FLAG(NWTO_RCV_URG),
|
||||||
|
FLAG(NWTO_RCV_NOTURG),
|
||||||
|
FLAG(NWTO_BSD_URG),
|
||||||
|
FLAG(NWTO_NOTBSD_URG),
|
||||||
|
FLAG(NWTO_DEL_RST),
|
||||||
|
FLAG(NWTO_BULK),
|
||||||
|
FLAG(NWTO_NOBULK),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags udpopt_flags[] = {
|
||||||
|
FLAG_ZERO(NWUO_NOFLAGS),
|
||||||
|
FLAG_MASK(NWUO_ACC_MASK, NWUO_EXCL),
|
||||||
|
FLAG_MASK(NWUO_ACC_MASK, NWUO_SHARED),
|
||||||
|
FLAG_MASK(NWUO_ACC_MASK, NWUO_COPY),
|
||||||
|
FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_SET),
|
||||||
|
FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_SEL),
|
||||||
|
FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_ANY),
|
||||||
|
FLAG(NWUO_EN_LOC),
|
||||||
|
FLAG(NWUO_DI_LOC),
|
||||||
|
FLAG(NWUO_EN_BROAD),
|
||||||
|
FLAG(NWUO_DI_BROAD),
|
||||||
|
FLAG(NWUO_RP_SET),
|
||||||
|
FLAG(NWUO_RP_ANY),
|
||||||
|
FLAG(NWUO_RA_SET),
|
||||||
|
FLAG(NWUO_RA_ANY),
|
||||||
|
FLAG(NWUO_RWDATONLY),
|
||||||
|
FLAG(NWUO_RWDATALL),
|
||||||
|
FLAG(NWUO_EN_IPOPT),
|
||||||
|
FLAG(NWUO_DI_IPOPT),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_family(struct trace_proc * proc, const char * name, int family)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
/* TODO: add all the other protocols */
|
||||||
|
switch (family) {
|
||||||
|
TEXT(AF_UNSPEC);
|
||||||
|
TEXT(AF_LOCAL);
|
||||||
|
TEXT(AF_INET);
|
||||||
|
TEXT(AF_INET6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", family);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags sock_type[] = {
|
||||||
|
FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_STREAM),
|
||||||
|
FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_DGRAM),
|
||||||
|
FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_RAW),
|
||||||
|
FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_RDM),
|
||||||
|
FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_SEQPACKET),
|
||||||
|
FLAG(SOCK_CLOEXEC),
|
||||||
|
FLAG(SOCK_NONBLOCK),
|
||||||
|
FLAG(SOCK_NOSIGPIPE),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_shutdown_how(struct trace_proc * proc, const char * name, int how)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (how) {
|
||||||
|
TEXT(SHUT_RD);
|
||||||
|
TEXT(SHUT_WR);
|
||||||
|
TEXT(SHUT_RDWR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", how);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_struct_uucred(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr)
|
||||||
|
{
|
||||||
|
struct uucred cred;
|
||||||
|
|
||||||
|
if (!put_open_struct(proc, name, flags, addr, &cred, sizeof(cred)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
put_value(proc, "cr_uid", "%u", cred.cr_uid);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "cr_gid", "%u", cred.cr_gid);
|
||||||
|
if (verbose > 1)
|
||||||
|
put_value(proc, "cr_ngroups", "%d", cred.cr_ngroups);
|
||||||
|
put_groups(proc, "cr_groups", PF_LOCADDR,
|
||||||
|
(vir_bytes)&cred.cr_groups, cred.cr_ngroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, verbose > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_cmsg_type(struct trace_proc * proc, const char * name, int type)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (type) {
|
||||||
|
TEXT(SCM_RIGHTS);
|
||||||
|
TEXT(SCM_CREDS);
|
||||||
|
TEXT(SCM_TIMESTAMP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_msg_control(struct trace_proc * proc, struct msg_control * ptr)
|
||||||
|
{
|
||||||
|
struct msghdr msg;
|
||||||
|
struct cmsghdr *cmsg;
|
||||||
|
size_t len;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (ptr->msg_controllen > sizeof(ptr->msg_control)) {
|
||||||
|
put_field(proc, NULL, "..");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_open(proc, NULL, PF_NONAME, "[", ", ");
|
||||||
|
|
||||||
|
memset(&msg, 0, sizeof(msg));
|
||||||
|
msg.msg_control = ptr->msg_control;
|
||||||
|
msg.msg_controllen = ptr->msg_controllen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: decide if we need a verbosity-based limit here. The argument
|
||||||
|
* in favor of printing everything is that upon receipt, SCM_RIGHTS
|
||||||
|
* actually creates new file descriptors, which is pretty essential in
|
||||||
|
* terms of figuring out what is happening in a process. In addition,
|
||||||
|
* these calls should be sufficiently rare that the lengthy output is
|
||||||
|
* not really disruptive for the general output flow.
|
||||||
|
*/
|
||||||
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
|
||||||
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
||||||
|
put_open(proc, NULL, 0, "{", ", ");
|
||||||
|
|
||||||
|
if (verbose > 0)
|
||||||
|
put_value(proc, "cmsg_len", "%u", cmsg->cmsg_len);
|
||||||
|
if (!valuesonly && cmsg->cmsg_level == SOL_SOCKET)
|
||||||
|
put_field(proc, "cmsg_level", "SOL_SOCKET");
|
||||||
|
else
|
||||||
|
put_value(proc, "cmsg_level", "%d", cmsg->cmsg_level);
|
||||||
|
if (cmsg->cmsg_level == SOL_SOCKET)
|
||||||
|
put_cmsg_type(proc, "cmsg_type", cmsg->cmsg_type);
|
||||||
|
|
||||||
|
len = cmsg->cmsg_len - CMSG_LEN(0);
|
||||||
|
|
||||||
|
/* Print the contents of the messages that we know. */
|
||||||
|
if (cmsg->cmsg_level == SOL_SOCKET &&
|
||||||
|
cmsg->cmsg_type == SCM_RIGHTS) {
|
||||||
|
put_open(proc, NULL, PF_NONAME, "[", ", ");
|
||||||
|
for (i = 0; i < len / sizeof(int); i++)
|
||||||
|
put_fd(proc, NULL,
|
||||||
|
((int *)CMSG_DATA(cmsg))[i]);
|
||||||
|
put_close(proc, "]");
|
||||||
|
} else if (cmsg->cmsg_level == SOL_SOCKET &&
|
||||||
|
cmsg->cmsg_type == SCM_CREDS) {
|
||||||
|
put_struct_uucred(proc, NULL, PF_LOCADDR,
|
||||||
|
(vir_bytes)CMSG_DATA(cmsg));
|
||||||
|
} else if (len > 0)
|
||||||
|
put_field(proc, NULL, "..");
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close(proc, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
net_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, int dir)
|
||||||
|
{
|
||||||
|
const char *text;
|
||||||
|
nwio_ipopt_t *ipopt;
|
||||||
|
nwio_tcpconf_t *nwtc;
|
||||||
|
nwio_tcpcl_t *nwtcl;
|
||||||
|
nwio_tcpopt_t *nwto;
|
||||||
|
tcp_cookie_t *cookie;
|
||||||
|
nwio_udpopt_t *nwuo;
|
||||||
|
struct sockaddr_un *sun;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
case FIONREAD:
|
||||||
|
/*
|
||||||
|
* Arguably this does not belong here, but as of writing, the
|
||||||
|
* network services are the only ones actually implementing
|
||||||
|
* support for this IOCTL, and we don't have a more suitable
|
||||||
|
* place to put it either.
|
||||||
|
*/
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSIPOPT:
|
||||||
|
case NWIOGIPOPT:
|
||||||
|
if ((ipopt = (nwio_ipopt_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, "nwio_flags", ipopt_flags, COUNT(ipopt_flags),
|
||||||
|
"0x%x", ipopt->nwio_flags);
|
||||||
|
|
||||||
|
if (ipopt->nwio_flags & NWIO_REMSPEC)
|
||||||
|
put_ipaddr(proc, "nwio_rem", ipopt->nwio_rem);
|
||||||
|
if (ipopt->nwio_flags & NWIO_PROTOSPEC)
|
||||||
|
put_ipproto(proc, "nwio_proto", ipopt->nwio_proto);
|
||||||
|
|
||||||
|
return 0; /* TODO: the remaining fields */
|
||||||
|
|
||||||
|
case NWIOSTCPCONF:
|
||||||
|
case NWIOGTCPCONF:
|
||||||
|
if ((nwtc = (nwio_tcpconf_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, "nwtc_flags", tcpconf_flags,
|
||||||
|
COUNT(tcpconf_flags), "0x%x", nwtc->nwtc_flags);
|
||||||
|
|
||||||
|
/* The local address cannot be set, just retrieved. */
|
||||||
|
if (req == NWIOGTCPCONF)
|
||||||
|
put_ipaddr(proc, "nwtc_locaddr", nwtc->nwtc_locaddr);
|
||||||
|
|
||||||
|
if ((nwtc->nwtc_flags & NWTC_LOCPORT_MASK) == NWTC_LP_SET)
|
||||||
|
put_port(proc, "nwtc_locport", nwtc->nwtc_locport);
|
||||||
|
|
||||||
|
if (nwtc->nwtc_flags & NWTC_SET_RA)
|
||||||
|
put_ipaddr(proc, "nwtc_remaddr", nwtc->nwtc_remaddr);
|
||||||
|
|
||||||
|
if (nwtc->nwtc_flags & NWTC_SET_RP)
|
||||||
|
put_port(proc, "nwtc_remport", nwtc->nwtc_remport);
|
||||||
|
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOTCPCONN:
|
||||||
|
case NWIOTCPLISTEN:
|
||||||
|
if ((nwtcl = (nwio_tcpcl_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, "nwtcl_flags", tcpcl_flags,
|
||||||
|
COUNT(tcpcl_flags), "0x%x", nwtcl->nwtcl_flags);
|
||||||
|
|
||||||
|
/* We pretend the unused nwtcl_ttl field does not exist. */
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSTCPOPT:
|
||||||
|
case NWIOGTCPOPT:
|
||||||
|
if ((nwto = (nwio_tcpopt_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, "nwto_flags", tcpopt_flags,
|
||||||
|
COUNT(tcpopt_flags), "0x%x", nwto->nwto_flags);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOTCPLISTENQ:
|
||||||
|
case NWIOSUDSBLOG:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%d", *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOGTCPCOOKIE:
|
||||||
|
case NWIOTCPACCEPTTO:
|
||||||
|
if ((cookie = (tcp_cookie_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_value(proc, "tc_ref", "%"PRIu32, cookie->tc_ref);
|
||||||
|
if (verbose > 0)
|
||||||
|
put_buf(proc, "tc_secret", PF_LOCADDR,
|
||||||
|
(vir_bytes)&cookie->tc_secret,
|
||||||
|
sizeof(cookie->tc_secret));
|
||||||
|
return (verbose > 0) ? IF_ALL : 0;
|
||||||
|
|
||||||
|
case NWIOTCPGERROR:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
i = *(int *)ptr;
|
||||||
|
if (!valuesonly && (text = get_error_name(i)) != NULL)
|
||||||
|
put_field(proc, NULL, text);
|
||||||
|
else
|
||||||
|
put_value(proc, NULL, "%d", i);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSUDPOPT:
|
||||||
|
case NWIOGUDPOPT:
|
||||||
|
if ((nwuo = (nwio_udpopt_t *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, "nwuo_flags", udpopt_flags,
|
||||||
|
COUNT(udpopt_flags), "0x%x", nwuo->nwuo_flags);
|
||||||
|
|
||||||
|
/* The local address cannot be set, just retrieved. */
|
||||||
|
if (req == NWIOGUDPOPT)
|
||||||
|
put_ipaddr(proc, "nwuo_locaddr", nwuo->nwuo_locaddr);
|
||||||
|
|
||||||
|
if ((nwuo->nwuo_flags & NWUO_LOCPORT_MASK) == NWUO_LP_SET)
|
||||||
|
put_port(proc, "nwuo_locport", nwuo->nwuo_locport);
|
||||||
|
|
||||||
|
if (nwuo->nwuo_flags & NWUO_RA_SET)
|
||||||
|
put_ipaddr(proc, "nwuo_remaddr", nwuo->nwuo_remaddr);
|
||||||
|
|
||||||
|
if (nwuo->nwuo_flags & NWUO_RP_SET)
|
||||||
|
put_port(proc, "nwuo_remport", nwuo->nwuo_remport);
|
||||||
|
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOGUDSFADDR:
|
||||||
|
case NWIOSUDSTADDR:
|
||||||
|
case NWIOSUDSADDR:
|
||||||
|
case NWIOGUDSADDR:
|
||||||
|
case NWIOGUDSPADDR:
|
||||||
|
case NWIOSUDSCONN:
|
||||||
|
case NWIOSUDSACCEPT:
|
||||||
|
if ((sun = (struct sockaddr_un *)ptr) == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_family(proc, "sun_family", sun->sun_family);
|
||||||
|
|
||||||
|
/* This could be extended to a generic sockaddr printer.. */
|
||||||
|
if (sun->sun_family == AF_LOCAL) {
|
||||||
|
put_buf(proc, "sun_path", PF_LOCADDR | PF_PATH,
|
||||||
|
(vir_bytes)&sun->sun_path, sizeof(sun->sun_path));
|
||||||
|
return IF_ALL; /* skipping sun_len, it's unused */
|
||||||
|
} else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case NWIOSUDSTYPE:
|
||||||
|
case NWIOGUDSSOTYPE:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_flags(proc, NULL, sock_type, COUNT(sock_type), "0x%x",
|
||||||
|
*(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSUDSSHUT:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_shutdown_how(proc, NULL, *(int *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSUDSPAIR:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_dev(proc, NULL, *(dev_t *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOSUDSCTRL:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
case NWIOGUDSCTRL:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_msg_control(proc, (struct msg_control *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOGUDSPEERCRED:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return IF_IN;
|
||||||
|
|
||||||
|
put_struct_uucred(proc, NULL, PF_LOCADDR, (vir_bytes)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case NWIOGUDSSNDBUF:
|
||||||
|
case NWIOSUDSSNDBUF:
|
||||||
|
case NWIOGUDSRCVBUF:
|
||||||
|
case NWIOSUDSRCVBUF:
|
||||||
|
if (ptr == NULL)
|
||||||
|
return dir;
|
||||||
|
|
||||||
|
put_value(proc, NULL, "%zu", *(size_t *)ptr);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
63
minix/usr.bin/trace/ioctl/svrctl.c
Normal file
63
minix/usr.bin/trace/ioctl/svrctl.c
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/svrctl.h>
|
||||||
|
|
||||||
|
const char *
|
||||||
|
svrctl_name(unsigned long req)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
NAME(PMSETPARAM);
|
||||||
|
NAME(PMGETPARAM);
|
||||||
|
NAME(VFSGETPARAM);
|
||||||
|
NAME(VFSSETPARAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
svrctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, int dir)
|
||||||
|
{
|
||||||
|
struct sysgetenv *env;
|
||||||
|
|
||||||
|
switch (req) {
|
||||||
|
case PMSETPARAM:
|
||||||
|
case VFSSETPARAM:
|
||||||
|
if ((env = (struct sysgetenv *)ptr) == NULL)
|
||||||
|
return IF_OUT;
|
||||||
|
|
||||||
|
put_buf(proc, "key", PF_STRING, (vir_bytes)env->key,
|
||||||
|
env->keylen);
|
||||||
|
put_buf(proc, "value", PF_STRING, (vir_bytes)env->val,
|
||||||
|
env->vallen);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
case PMGETPARAM:
|
||||||
|
case VFSGETPARAM:
|
||||||
|
if ((env = (struct sysgetenv *)ptr) == NULL)
|
||||||
|
return IF_OUT | IF_IN;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* So far this is the only IOCTL case where the output depends
|
||||||
|
* on one of the values in the input: if the given key is NULL,
|
||||||
|
* PM provides the entire system environment in return, which
|
||||||
|
* means we cannot just print a single string. We rely on PM
|
||||||
|
* not changing the key field, which (while true) is an
|
||||||
|
* assumption. With the current (simple) model we would have
|
||||||
|
* to save the provided key pointer somewhere otherwise.
|
||||||
|
*/
|
||||||
|
if (dir == IF_OUT)
|
||||||
|
put_buf(proc, "key", PF_STRING, (vir_bytes)env->key,
|
||||||
|
env->keylen);
|
||||||
|
else
|
||||||
|
put_buf(proc, "value",
|
||||||
|
(env->key != NULL) ? PF_STRING : 0,
|
||||||
|
(vir_bytes)env->val, env->vallen);
|
||||||
|
return IF_ALL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
307
minix/usr.bin/trace/kernel.c
Normal file
307
minix/usr.bin/trace/kernel.c
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
/*
|
||||||
|
* This file, and only this file, should contain all the ugliness needed to
|
||||||
|
* obtain values from the kernel. It has to be recompiled every time the
|
||||||
|
* layout of the kernel "struct proc" and/or "struct priv" structures changes.
|
||||||
|
* In addition, this file contains the platform-dependent code related to
|
||||||
|
* interpreting the registers exposed by the kernel.
|
||||||
|
*
|
||||||
|
* As a quick note, some functions return TRUE/FALSE, and some return 0/-1.
|
||||||
|
* The former convention is used for functions that return a boolean value;
|
||||||
|
* the latter is used for functions that set errno in all cases of failure,
|
||||||
|
* and where the caller may conceivably use errno as a result.
|
||||||
|
*
|
||||||
|
* On a related note, relevant here and elsewhere: we define _MINIX_SYSTEM but
|
||||||
|
* not _SYSTEM, which means that we should not get negative error numbers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <machine/archtypes.h>
|
||||||
|
#include <minix/timers.h>
|
||||||
|
#include "kernel/proc.h"
|
||||||
|
#include "kernel/priv.h"
|
||||||
|
#if defined(__i386__)
|
||||||
|
#include "kernel/arch/i386/include/archconst.h" /* for the KTS_ constants */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <minix/param.h>
|
||||||
|
|
||||||
|
extern struct minix_kerninfo *_minix_kerninfo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Working area. By obtaining values from the kernel into these local process
|
||||||
|
* structures, and then returning them, we gain a little robustness against
|
||||||
|
* changes in data types of the fields we need.
|
||||||
|
*/
|
||||||
|
static struct proc kernel_proc;
|
||||||
|
static struct priv kernel_priv;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether our notion of the kernel process structure layout matches that
|
||||||
|
* of the kernel, by comparing magic values. This can be done only once we
|
||||||
|
* have attached to a process. Return TRUE if everything seems alright; FALSE
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_check(pid_t pid)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (mem_get_user(pid, offsetof(struct proc, p_magic),
|
||||||
|
&kernel_proc.p_magic, sizeof(kernel_proc.p_magic)) < 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return (kernel_proc.p_magic == PMAGIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtain the kernel name for the given (stopped) process. Return 0 on
|
||||||
|
* success, with the (possibly truncated) name stored in the 'name' buffer
|
||||||
|
* which is of 'size' bytes; the name will be null-terminated. Note that the
|
||||||
|
* name may contain any suffixes as set by the kernel. Return -1 on failure,
|
||||||
|
* with errno set as appropriate.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_get_name(pid_t pid, char * name, size_t size)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (mem_get_user(pid, offsetof(struct proc, p_name),
|
||||||
|
kernel_proc.p_name, sizeof(kernel_proc.p_name)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
strlcpy(name, kernel_proc.p_name, size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether the given process, which we have just attached to, is a system
|
||||||
|
* service. PM does not prevent us from attaching to most system services,
|
||||||
|
* even though this utility only supports tracing user programs. Unlike a few
|
||||||
|
* other routines in this file, this function can not use ProcFS to obtain its
|
||||||
|
* result, because the given process may actually be VFS or ProcFS itself!
|
||||||
|
* Return TRUE if the given process is a system service; FALSE if not.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_is_service(pid_t pid)
|
||||||
|
{
|
||||||
|
size_t align, off;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For T_GETUSER, the priv structure follows the proc structure, but
|
||||||
|
* possibly with padding in between so as to align the priv structure
|
||||||
|
* to long boundary.
|
||||||
|
*/
|
||||||
|
align = sizeof(long) - 1;
|
||||||
|
off = (sizeof(struct proc) + align) & ~align;
|
||||||
|
|
||||||
|
if (mem_get_user(pid, off + offsetof(struct priv, s_id),
|
||||||
|
&kernel_priv.s_id, sizeof(kernel_priv.s_id)) < 0)
|
||||||
|
return FALSE; /* process may have disappeared, so no danger */
|
||||||
|
|
||||||
|
return (kernel_priv.s_id != USER_PRIV_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the given process, which must be stopped on entering a system call,
|
||||||
|
* retrieve the three register values describing the system call. Return 0 on
|
||||||
|
* success, or -1 on failure with errno set as appropriate.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_get_syscall(pid_t pid, reg_t reg[3])
|
||||||
|
{
|
||||||
|
|
||||||
|
assert(sizeof(kernel_proc.p_defer) == sizeof(reg_t) * 3);
|
||||||
|
|
||||||
|
if (mem_get_user(pid, offsetof(struct proc, p_defer),
|
||||||
|
&kernel_proc.p_defer, sizeof(kernel_proc.p_defer)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
reg[0] = kernel_proc.p_defer.r1;
|
||||||
|
reg[1] = kernel_proc.p_defer.r2;
|
||||||
|
reg[2] = kernel_proc.p_defer.r3;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieve the value of the primary return register for the given process,
|
||||||
|
* which must be stopped on leaving a system call. This register contains the
|
||||||
|
* IPC-level result of the system call. Return 0 on success, or -1 on failure
|
||||||
|
* with errno set as appropriate.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_get_retreg(pid_t pid, reg_t * retreg)
|
||||||
|
{
|
||||||
|
size_t off;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Historically p_reg had to be the first field in the proc structure,
|
||||||
|
* but since this is no longer a hard requirement, getting its actual
|
||||||
|
* offset into the proc structure certainly doesn't hurt.
|
||||||
|
*/
|
||||||
|
off = offsetof(struct proc, p_reg);
|
||||||
|
|
||||||
|
if (mem_get_user(pid, off + offsetof(struct stackframe_s, retreg),
|
||||||
|
&kernel_proc.p_reg.retreg, sizeof(kernel_proc.p_reg.retreg)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*retreg = kernel_proc.p_reg.retreg;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the stack top for user processes. This is needed for execve(), since
|
||||||
|
* the supplied frame contains pointers prepared for the new location of the
|
||||||
|
* frame, which is at the stack top of the process after the execve().
|
||||||
|
*/
|
||||||
|
vir_bytes
|
||||||
|
kernel_get_stacktop(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
return _minix_kerninfo->kinfo->user_sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the given stopped process, get its program counter (pc), stack pointer
|
||||||
|
* (sp), and optionally its frame pointer (fp). The given fp pointer may be
|
||||||
|
* NULL, in which case the frame pointer is not obtained. The given pc and sp
|
||||||
|
* pointers must not be NULL, and this is intentional: obtaining fp may require
|
||||||
|
* obtaining sp first. Return 0 on success, or -1 on failure with errno set
|
||||||
|
* as appropriate. This functionality is not essential for tracing processes,
|
||||||
|
* and may not be supported on all platforms, in part or full. In particular,
|
||||||
|
* on some platforms, a zero (= invalid) frame pointer may be returned on
|
||||||
|
* success, indicating that obtaining frame pointers is not supported.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
kernel_get_context(pid_t pid, reg_t * pc, reg_t * sp, reg_t * fp)
|
||||||
|
{
|
||||||
|
size_t off;
|
||||||
|
|
||||||
|
off = offsetof(struct proc, p_reg); /* as above */
|
||||||
|
|
||||||
|
if (mem_get_user(pid, off + offsetof(struct stackframe_s, pc),
|
||||||
|
&kernel_proc.p_reg.pc, sizeof(kernel_proc.p_reg.pc)) < 0)
|
||||||
|
return -1;
|
||||||
|
if (mem_get_user(pid, off + offsetof(struct stackframe_s, sp),
|
||||||
|
&kernel_proc.p_reg.sp, sizeof(kernel_proc.p_reg.sp)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*pc = kernel_proc.p_reg.pc;
|
||||||
|
*sp = kernel_proc.p_reg.sp;
|
||||||
|
|
||||||
|
if (fp == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#if defined(__i386__)
|
||||||
|
if (mem_get_user(pid, offsetof(struct proc, p_seg) +
|
||||||
|
offsetof(struct segframe, p_kern_trap_style),
|
||||||
|
&kernel_proc.p_seg.p_kern_trap_style,
|
||||||
|
sizeof(kernel_proc.p_seg.p_kern_trap_style)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* This is taken from the kernel i386 exception code. */
|
||||||
|
switch (kernel_proc.p_seg.p_kern_trap_style) {
|
||||||
|
case KTS_SYSENTER:
|
||||||
|
case KTS_SYSCALL:
|
||||||
|
if (mem_get_data(pid, *sp + 16, fp, sizeof(fp)) < 0)
|
||||||
|
return -1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (mem_get_user(pid, off + offsetof(struct stackframe_s, fp),
|
||||||
|
&kernel_proc.p_reg.fp, sizeof(kernel_proc.p_reg.fp)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*fp = kernel_proc.p_reg.fp;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
*fp = 0; /* not supported; this is not a failure (*pc is valid) */
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a frame pointer, obtain the next program counter and frame pointer.
|
||||||
|
* Return 0 if successful, or -1 on failure with errno set appropriately. The
|
||||||
|
* functionality is not essential for tracing processes, and may not be
|
||||||
|
* supported on all platforms. Thus, on some platforms, this function may
|
||||||
|
* always fail.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
kernel_get_nextframe(pid_t pid, reg_t fp, reg_t * next_pc, reg_t * next_fp)
|
||||||
|
{
|
||||||
|
#if defined(__i386__)
|
||||||
|
void *p[2];
|
||||||
|
|
||||||
|
if (mem_get_data(pid, (vir_bytes)fp, &p, sizeof(p)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*next_pc = (reg_t)p[1];
|
||||||
|
*next_fp = (reg_t)p[0];
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
/* Not supported (yet). */
|
||||||
|
errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a stack trace for the given process, which is known to be stopped on
|
||||||
|
* entering a system call. This function does not really belong here, but
|
||||||
|
* without a doubt it is going to have to be fully rewritten to support
|
||||||
|
* anything other than i386.
|
||||||
|
*
|
||||||
|
* Getting symbol names is currently an absolute nightmare. Not just because
|
||||||
|
* of shared libraries, but also since ProcFS does not offer a /proc/NNN/exe,
|
||||||
|
* so that we cannot reliably determine the binary being executed: not for
|
||||||
|
* processes being attached to, and not for exec calls using a relative path.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
kernel_put_stacktrace(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
unsigned int count, max;
|
||||||
|
reg_t pc, sp, fp, low, high;
|
||||||
|
|
||||||
|
if (kernel_get_context(proc->pid, &pc, &sp, &fp) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A low default limit such as 6 looks much prettier, but is simply not
|
||||||
|
* useful enough for moderately-sized programs in practice. Right now,
|
||||||
|
* 15 is about two lines on a 80-column terminal.
|
||||||
|
*/
|
||||||
|
if (verbose == 0) max = 15;
|
||||||
|
else if (verbose == 1) max = 31;
|
||||||
|
else max = UINT_MAX;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We keep formatting to an absolute minimum, to facilitate passing
|
||||||
|
* the lines straight into tools such as addr2line.
|
||||||
|
*/
|
||||||
|
put_newline();
|
||||||
|
put_fmt(proc, " 0x%x", pc);
|
||||||
|
|
||||||
|
low = high = fp;
|
||||||
|
|
||||||
|
for (count = 1; count < max && fp != 0; count++) {
|
||||||
|
if (kernel_get_nextframe(proc->pid, fp, &pc, &fp) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
put_fmt(proc, " 0x%x", pc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stop if we see a frame pointer that falls within the range
|
||||||
|
* of the frame pointers we have seen so far. This also
|
||||||
|
* prevents getting stuck in a loop on the same frame pointer.
|
||||||
|
*/
|
||||||
|
if (fp >= low && fp <= high)
|
||||||
|
break;
|
||||||
|
if (low > fp)
|
||||||
|
low = fp;
|
||||||
|
if (high < fp)
|
||||||
|
high = fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp != 0)
|
||||||
|
put_text(proc, " ..");
|
||||||
|
put_newline();
|
||||||
|
}
|
61
minix/usr.bin/trace/mem.c
Normal file
61
minix/usr.bin/trace/mem.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieve 'len' bytes from the memory of the traced process 'pid' at address
|
||||||
|
* 'addr' and put the result in the buffer pointed to by 'ptr'. Return 0 on
|
||||||
|
* success, or otherwise -1 with errno set appropriately.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mem_get_data(pid_t pid, vir_bytes addr, void * ptr, size_t len)
|
||||||
|
{
|
||||||
|
struct ptrace_range pr;
|
||||||
|
|
||||||
|
if (len == 0) return 0;
|
||||||
|
|
||||||
|
pr.pr_space = TS_DATA;
|
||||||
|
pr.pr_addr = addr;
|
||||||
|
pr.pr_size = len;
|
||||||
|
pr.pr_ptr = ptr;
|
||||||
|
|
||||||
|
return ptrace(T_GETRANGE, pid, &pr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieve 'len' bytes from the kernel structure memory of the traced process
|
||||||
|
* 'pid' at offset 'addr' and put the result in the buffer pointed to by 'ptr'.
|
||||||
|
* Return 0 on success, or otherwise -1 with errno set appropriately.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mem_get_user(pid_t pid, vir_bytes addr, void * ptr, size_t len)
|
||||||
|
{
|
||||||
|
long data;
|
||||||
|
char *p;
|
||||||
|
size_t off, chunk;
|
||||||
|
|
||||||
|
if (len == 0) return 0;
|
||||||
|
|
||||||
|
/* Align access to address. */
|
||||||
|
off = addr & (sizeof(data) - 1);
|
||||||
|
addr -= off;
|
||||||
|
|
||||||
|
p = ptr;
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
errno = 0;
|
||||||
|
data = ptrace(T_GETUSER, pid, (void *)addr, 0);
|
||||||
|
if (errno != 0) return -1;
|
||||||
|
|
||||||
|
chunk = sizeof(data) - off;
|
||||||
|
if (chunk > len)
|
||||||
|
chunk = len;
|
||||||
|
|
||||||
|
memcpy(p, (char *)&data + off, chunk);
|
||||||
|
p += chunk;
|
||||||
|
addr += chunk;
|
||||||
|
len -= chunk;
|
||||||
|
off = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
516
minix/usr.bin/trace/output.c
Normal file
516
minix/usr.bin/trace/output.c
Normal file
|
@ -0,0 +1,516 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum number of bytes that may be buffered before writing the buffered
|
||||||
|
* output to the underlying file. This is a performance optimization only.
|
||||||
|
* Writing more than this number of bytes at once will be handled correctly.
|
||||||
|
*/
|
||||||
|
#define OUTPUT_BUFSZ 512
|
||||||
|
|
||||||
|
static int out_fd;
|
||||||
|
static char out_buf[OUTPUT_BUFSZ];
|
||||||
|
static int out_len;
|
||||||
|
static int out_err;
|
||||||
|
|
||||||
|
static pid_t last_pid; /* not a trace_proc pointer; it could become invalid! */
|
||||||
|
static unsigned int line_off;
|
||||||
|
static unsigned int prefix_off;
|
||||||
|
static int print_pid;
|
||||||
|
static int print_susp;
|
||||||
|
static int add_space;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the output channel. Called before any other output functions,
|
||||||
|
* but after a child process (to be traced) has already been spawned. If the
|
||||||
|
* given file string is not NULL, it is the path to a file that is to be used
|
||||||
|
* to write output to. If it is NULL, output is written to standard error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
output_init(const char * file)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Initialize state. */
|
||||||
|
out_len = 0;
|
||||||
|
out_err = FALSE;
|
||||||
|
|
||||||
|
last_pid = 0;
|
||||||
|
line_off = 0;
|
||||||
|
prefix_off = 0;
|
||||||
|
print_pid = FALSE;
|
||||||
|
print_susp = FALSE;
|
||||||
|
add_space = FALSE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore signals resulting from writing to a closed pipe. We can
|
||||||
|
* handle write errors properly ourselves. Setting O_NOSIGPIPE is an
|
||||||
|
* alternative, but that would affect other processes writing to the
|
||||||
|
* same file object, even after we have terminated.
|
||||||
|
*/
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
/* Initialize the output file descriptor. */
|
||||||
|
if (file == NULL) {
|
||||||
|
/* No output file given? Use standard error. */
|
||||||
|
out_fd = STDERR_FILENO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Use a restrictive mask for the output file. Traces may
|
||||||
|
* contain sensitive information (for security and otherwise),
|
||||||
|
* and the user might not always be careful about the location
|
||||||
|
* of the file.
|
||||||
|
*/
|
||||||
|
/* The file descriptor is not closed explicitly. */
|
||||||
|
out_fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
|
||||||
|
0600);
|
||||||
|
|
||||||
|
return (out_fd < 0) ? -1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the given data to the given file descriptor, taking into account the
|
||||||
|
* possibility of partial writes and write errors.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
write_fd(int fd, const char *buf, size_t len)
|
||||||
|
{
|
||||||
|
ssize_t r;
|
||||||
|
|
||||||
|
/* If we got a write error before, do not try to write more. */
|
||||||
|
if (out_err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Write all output, in chunks if we have to. */
|
||||||
|
while (len > 0) {
|
||||||
|
r = write(fd, buf, len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A write error (and that includes EOF) causes the program to
|
||||||
|
* terminate with an error code. For obvious reasons we cannot
|
||||||
|
* print an error about this. Do not even report to standard
|
||||||
|
* error if the output was redirected, because that may mess
|
||||||
|
* with the actual programs being run right now.
|
||||||
|
*/
|
||||||
|
if (r <= 0) {
|
||||||
|
out_err = TRUE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
len -= r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return TRUE iff an output error occurred and the program should terminate.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
output_error(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
return out_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print the given null-terminated string to the output channel. Return the
|
||||||
|
* number of characters printed, for alignment purposes. In the future, this
|
||||||
|
* number may end up being different from the number of bytes given to print,
|
||||||
|
* due to multibyte encoding or colors or whatnot.
|
||||||
|
*/
|
||||||
|
static unsigned int
|
||||||
|
output_write(const char * text)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
len = strlen(text);
|
||||||
|
|
||||||
|
if (out_len + len > sizeof(out_buf)) {
|
||||||
|
write_fd(out_fd, out_buf, out_len);
|
||||||
|
|
||||||
|
out_len = 0;
|
||||||
|
|
||||||
|
/* Write large buffers right away. */
|
||||||
|
if (len > sizeof(out_buf)) {
|
||||||
|
write_fd(out_fd, text, len);
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&out_buf[out_len], text, len);
|
||||||
|
|
||||||
|
out_len += len;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush any pending output to the output channel.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
output_flush(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (out_len > 0) {
|
||||||
|
write_fd(out_fd, out_buf, out_len);
|
||||||
|
|
||||||
|
out_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a PID prefix for the given process, or an info prefix if no process
|
||||||
|
* (NULL) is given. Prefixes are only relevant when multiple processes are
|
||||||
|
* traced. As long as there are multiple processes, each line is prefixed with
|
||||||
|
* the PID of the process. As soon as the number of processes has been reduced
|
||||||
|
* back to one, one more line is prefixed with the PID of the remaining process
|
||||||
|
* (with a "'" instead of a "|") to help the user identify which process is
|
||||||
|
* left. In addition, whenever a preempted call is about to be resumed, a "*"
|
||||||
|
* is printed instead of a space, so as to show that it is a continuation of a
|
||||||
|
* previous line. An example of all these cases:
|
||||||
|
*
|
||||||
|
* fork() = 3
|
||||||
|
* 3| Tracing test (pid 3)
|
||||||
|
* 3| fork() = 0
|
||||||
|
* 3| read(0, <..>
|
||||||
|
* 2| waitpid(-1, <..>
|
||||||
|
* INFO| This is an example info line.
|
||||||
|
* 3|*read(0, "", 1024) = 0
|
||||||
|
* 3| exit(1)
|
||||||
|
* 3| Process exited normally with code 1
|
||||||
|
* 2'*waitpid(-1, W_EXITED(1), 0) = 3
|
||||||
|
* exit(0)
|
||||||
|
* Process exited normally with code 0
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
put_prefix(struct trace_proc * proc, int resuming)
|
||||||
|
{
|
||||||
|
char prefix[32];
|
||||||
|
unsigned int count;
|
||||||
|
|
||||||
|
assert(line_off == 0);
|
||||||
|
|
||||||
|
count = proc_count();
|
||||||
|
|
||||||
|
/* TODO: add a command line option for always printing the pid. */
|
||||||
|
if (print_pid || count > 1 || proc == NULL) {
|
||||||
|
/*
|
||||||
|
* TODO: we currently rely on the highest PID having at most
|
||||||
|
* five digits, but this will eventually change. There are
|
||||||
|
* several ways to deal with that, but none are great.
|
||||||
|
*/
|
||||||
|
if (proc == NULL)
|
||||||
|
snprintf(prefix, sizeof(prefix), "%5s| ", "INFO");
|
||||||
|
else
|
||||||
|
snprintf(prefix, sizeof(prefix), "%5d%c%c",
|
||||||
|
proc->pid, (count > 1) ? '|' : '\'',
|
||||||
|
resuming ? '*' : ' ');
|
||||||
|
|
||||||
|
prefix_off = line_off = output_write(prefix);
|
||||||
|
|
||||||
|
last_pid = (proc != NULL ? proc->pid : 0);
|
||||||
|
} else {
|
||||||
|
assert(!resuming);
|
||||||
|
|
||||||
|
prefix_off = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remember whether the next line should get prefixed regardless. */
|
||||||
|
print_pid = (count > 1 || proc == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a string to the end of the text recording for the given process.
|
||||||
|
* This is used only to record the call-enter output of system calls.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
record_add(struct trace_proc * proc, const char * text)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
assert(proc->recording);
|
||||||
|
|
||||||
|
/* If the recording buffer is already full, do not record more. */
|
||||||
|
if (proc->outlen == sizeof(proc->outbuf))
|
||||||
|
return;
|
||||||
|
|
||||||
|
len = strlen(text);
|
||||||
|
|
||||||
|
/* If nonempty, the recording buffer is always null terminated. */
|
||||||
|
if (len < sizeof(proc->outbuf) - proc->outlen - 1) {
|
||||||
|
strcpy(&proc->outbuf[proc->outlen], text);
|
||||||
|
|
||||||
|
proc->outlen += len;
|
||||||
|
} else
|
||||||
|
proc->outlen = sizeof(proc->outbuf); /* buffer exhausted */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start recording text for the given process. Since this marks the start of
|
||||||
|
* a call, remember to print a preemption marker when the call gets preempted.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
record_start(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
proc->recording = TRUE;
|
||||||
|
|
||||||
|
print_susp = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stop recording text for the given process.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
record_stop(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
proc->recording = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear recorded text for the given process. Since this also marks the end of
|
||||||
|
* the entire call, no longer print a supension marker before the next newline.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
record_clear(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
assert(!proc->recording);
|
||||||
|
proc->outlen = 0;
|
||||||
|
|
||||||
|
if (proc->pid == last_pid)
|
||||||
|
print_susp = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replay the record for the given process on a new line, if the current line
|
||||||
|
* does not already have output for this process. If it does, do nothing.
|
||||||
|
* If the process has no recorded output, just start a new line. Return TRUE
|
||||||
|
* iff the caller must print its own replay text due to a recording overflow.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
record_replay(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
int space;
|
||||||
|
|
||||||
|
assert(!proc->recording);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is output on the current line, and it is for the current
|
||||||
|
* process, we must assume that it is the original, recorded text, and
|
||||||
|
* thus, we should do nothing. If output on the current line is for
|
||||||
|
* another process, we must force a new line before replaying.
|
||||||
|
*/
|
||||||
|
if (line_off > 0) {
|
||||||
|
if (proc->pid == last_pid)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
put_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is nothing to replay, do nothing further. This case may
|
||||||
|
* occur when printing signals, in which case the caller still expects
|
||||||
|
* a new line to be started. This line must not be prefixed with a
|
||||||
|
* "resuming" marker though--after all, nothing is being resumed here.
|
||||||
|
*/
|
||||||
|
if (proc->outlen == 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is text to replay, then this does mean we are in effect
|
||||||
|
* resuming the recorded call, even if it is just to print a signal.
|
||||||
|
* Thus, we must print a prefix that shows the call is being resumed.
|
||||||
|
* Similarly, unless the recording is cleared before a newline, we must
|
||||||
|
* suspend the line again, too.
|
||||||
|
*/
|
||||||
|
put_prefix(proc, TRUE /*resuming*/);
|
||||||
|
|
||||||
|
print_susp = TRUE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the recording buffer was exhausted during recording, the caller
|
||||||
|
* must generate the replay text instead.
|
||||||
|
*/
|
||||||
|
if (proc->outlen == sizeof(proc->outbuf))
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replay the recording. If it ends with a space, turn it into a soft
|
||||||
|
* space, because the recording may be followed immediately by a
|
||||||
|
* newline; an example of this is the exit() exception.
|
||||||
|
*/
|
||||||
|
space = proc->outbuf[proc->outlen - 1] == ' ';
|
||||||
|
if (space)
|
||||||
|
proc->outbuf[proc->outlen - 1] = 0;
|
||||||
|
|
||||||
|
put_text(proc, proc->outbuf);
|
||||||
|
|
||||||
|
if (space) {
|
||||||
|
put_space(proc);
|
||||||
|
|
||||||
|
/* Restore the space, in case another replay takes place. */
|
||||||
|
proc->outbuf[proc->outlen - 1] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start a new line, and adjust the local state accordingly. If nothing has
|
||||||
|
* been printed on the current line yet, this function is a no-op. Otherwise,
|
||||||
|
* the output so far may have to be marked as preempted with the "<..>"
|
||||||
|
* preemption marker.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_newline(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (line_off == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (print_susp) {
|
||||||
|
if (add_space)
|
||||||
|
(void)output_write(" ");
|
||||||
|
|
||||||
|
(void)output_write("<..>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
(void)output_write("|");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(void)output_write("\n");
|
||||||
|
output_flush();
|
||||||
|
|
||||||
|
line_off = 0;
|
||||||
|
add_space = FALSE;
|
||||||
|
print_susp = FALSE;
|
||||||
|
last_pid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a string as part of the output associated with a process. If the
|
||||||
|
* current line contains output for another process, a newline will be printed
|
||||||
|
* first. If the current line contains output for the same process, then the
|
||||||
|
* text will simply continue on the same line. If the current line is empty,
|
||||||
|
* a process PID prefix may have to be printed first. Either way, after this
|
||||||
|
* operation, the current line will contain text for the given process. If
|
||||||
|
* requested, the text may also be recorded for the process, for later replay.
|
||||||
|
* As an exception, proc may be NULL when printing general information lines.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_text(struct trace_proc * proc, const char * text)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (line_off > 0 && (proc == NULL || proc->pid != last_pid)) {
|
||||||
|
/*
|
||||||
|
* The current line has not been terminated with a newline yet.
|
||||||
|
* Start a new line. Note that this means that for lines not
|
||||||
|
* associated to a process, the whole line must be printed at
|
||||||
|
* once. This can be fixed but is currently not an issue.
|
||||||
|
*/
|
||||||
|
put_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See if we must add a prefix at the start of the line. */
|
||||||
|
if (line_off == 0)
|
||||||
|
put_prefix(proc, FALSE /*resuming*/);
|
||||||
|
|
||||||
|
/* If needed, record the given text. */
|
||||||
|
if (proc != NULL && proc->recording)
|
||||||
|
record_add(proc, text);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we delayed printing a space, print one now. This is never part
|
||||||
|
* of text that must be saved. In fact, we support these soft spaces
|
||||||
|
* for exactly one case; see put_space() for details.
|
||||||
|
*/
|
||||||
|
if (add_space) {
|
||||||
|
line_off += output_write(" ");
|
||||||
|
|
||||||
|
add_space = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, print the actual text. */
|
||||||
|
line_off += output_write(text);
|
||||||
|
|
||||||
|
last_pid = (proc != NULL) ? proc->pid : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a space to the output for the given process, but only if and once more
|
||||||
|
* text is printed for the process afterwards. The aim is to ensure that no
|
||||||
|
* lines ever end with a space, to prevent needless line wrapping on terminals.
|
||||||
|
* The space may have to be remembered for the current line (for preemption,
|
||||||
|
* which does not have a process pointer to work with) as well as recorded for
|
||||||
|
* later replay, if recording is enabled. Consider the following example:
|
||||||
|
*
|
||||||
|
* [A] 3| execve(..) <..>
|
||||||
|
* 2| getpid(0) = 2 (ppid=1)
|
||||||
|
* [B] 3| execve(..) = -1 [ENOENT]
|
||||||
|
* [A] 3| exit(1) <..>
|
||||||
|
* 2| getpid(0) = 2 (ppid=1)
|
||||||
|
* 3| exit(1)
|
||||||
|
* 3| Process exited normally with code 1
|
||||||
|
*
|
||||||
|
* On the [A] lines, the space between the call's closing parenthesis and the
|
||||||
|
* "<..>" preemption marker is the result of add_space being set to TRUE; on
|
||||||
|
* the [B] line, the space between the closing parenthesis and the equals sign
|
||||||
|
* is the result of the space being recorded.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
put_space(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* This call must only be used after output for the given process. */
|
||||||
|
assert(last_pid == proc->pid);
|
||||||
|
|
||||||
|
/* In case the call does not get preempted. */
|
||||||
|
add_space = TRUE;
|
||||||
|
|
||||||
|
/* In case the call does get preempted. */
|
||||||
|
if (proc->recording)
|
||||||
|
record_add(proc, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indent the remainders of the text on the line for this process, such that
|
||||||
|
* similar remainders are similarly aligned. In particular, the remainder is
|
||||||
|
* the equals sign of a call, and everything after it. Of course, alignment
|
||||||
|
* can only be used if the call has not already printed beyond the alignment
|
||||||
|
* position. Also, the prefix must not be counted toward the alignment, as it
|
||||||
|
* is possible that a line without prefix may be preempted and later continued
|
||||||
|
* with prefix. All things considered, the result would look like this:
|
||||||
|
*
|
||||||
|
* getuid() = 1 (euid=1)
|
||||||
|
* setuid(0) = -1 [EPERM]
|
||||||
|
* write(2, "Permission denied\n", 18) = 18
|
||||||
|
* fork() = 3
|
||||||
|
* 3| Tracing test (pid 3)
|
||||||
|
* 3| fork() = 0
|
||||||
|
* 3| exit(0)
|
||||||
|
* 3| Process exited normally with code 0
|
||||||
|
* 2' waitpid(-1, W_EXITED(0), 0) = 3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void put_align(struct trace_proc * __unused proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: add actual support for this. The following code works,
|
||||||
|
* although not so efficiently. The difficulty is the default
|
||||||
|
* configuration and corresponding options.
|
||||||
|
|
||||||
|
while (line_off - prefix_off < 20)
|
||||||
|
put_text(proc, " ");
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
97
minix/usr.bin/trace/proc.c
Normal file
97
minix/usr.bin/trace/proc.c
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
static TAILQ_HEAD(, trace_proc) proc_root;
|
||||||
|
static unsigned int nr_procs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the list of traced processes.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
proc_init(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
TAILQ_INIT(&proc_root);
|
||||||
|
nr_procs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a new process to the list of traced processes, allocating memory for it
|
||||||
|
* first. Return the new process structure with its PID assigned and the rest
|
||||||
|
* zeroed out, or NULL upon allocation failure (with errno set appropriately).
|
||||||
|
*/
|
||||||
|
struct trace_proc *
|
||||||
|
proc_add(pid_t pid)
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
|
||||||
|
proc = (struct trace_proc *)malloc(sizeof(struct trace_proc));
|
||||||
|
|
||||||
|
if (proc == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memset(proc, 0, sizeof(*proc));
|
||||||
|
|
||||||
|
proc->pid = pid;
|
||||||
|
|
||||||
|
TAILQ_INSERT_TAIL(&proc_root, proc, next);
|
||||||
|
nr_procs++;
|
||||||
|
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieve the data structure for a traced process based on its PID. Return
|
||||||
|
* a pointer to the structure, or NULL if no structure exists for this process.
|
||||||
|
*/
|
||||||
|
struct trace_proc *
|
||||||
|
proc_get(pid_t pid)
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
|
||||||
|
/* Linear search for now; se we can easily add a hashtable later.. */
|
||||||
|
TAILQ_FOREACH(proc, &proc_root, next) {
|
||||||
|
if (proc->pid == pid)
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a process from the list of traced processes.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
proc_del(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
TAILQ_REMOVE(&proc_root, proc, next);
|
||||||
|
nr_procs--;
|
||||||
|
|
||||||
|
free(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterator for the list of traced processes. If a NULL pointer is given,
|
||||||
|
* return the first process in the list; otherwise, return the next process in
|
||||||
|
* the list. Not stable with respect to list modifications.
|
||||||
|
*/
|
||||||
|
struct trace_proc *
|
||||||
|
proc_next(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (proc == NULL)
|
||||||
|
return TAILQ_FIRST(&proc_root);
|
||||||
|
else
|
||||||
|
return TAILQ_NEXT(proc, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the number of processes in the list of traced processes.
|
||||||
|
*/
|
||||||
|
unsigned int
|
||||||
|
proc_count(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
return nr_procs;
|
||||||
|
}
|
99
minix/usr.bin/trace/proc.h
Normal file
99
minix/usr.bin/trace/proc.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
|
||||||
|
#include <sys/queue.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum nesting depth of parentheses/brackets. The current maximum
|
||||||
|
* depth is something like six, for UDS control messages. This constant can be
|
||||||
|
* increased as necessary without any problem.
|
||||||
|
*/
|
||||||
|
#define MAX_DEPTH 10
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum size of text that may be recorded, including null terminator.
|
||||||
|
* Increasing this allows longer lines to be recorded and replayed without
|
||||||
|
* being cut short (see call_replay), but also increases memory usage.
|
||||||
|
*/
|
||||||
|
#define RECORD_BUFSZ 256
|
||||||
|
|
||||||
|
struct trace_proc {
|
||||||
|
/* identity (public) */
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
/* data structure management (proc.c) */
|
||||||
|
TAILQ_ENTRY(trace_proc) next;
|
||||||
|
|
||||||
|
/* general process state (trace.c) */
|
||||||
|
char name[PROC_NAME_LEN];
|
||||||
|
unsigned int trace_flags;
|
||||||
|
reg_t last_pc;
|
||||||
|
reg_t last_sp;
|
||||||
|
|
||||||
|
/* call enter-to-leave state (call.c) */
|
||||||
|
int call_type;
|
||||||
|
vir_bytes m_addr;
|
||||||
|
message m_out;
|
||||||
|
const char *call_name;
|
||||||
|
unsigned int call_flags;
|
||||||
|
const struct call_handler *call_handler;
|
||||||
|
int call_result;
|
||||||
|
|
||||||
|
/* output state (output.c) */
|
||||||
|
int recording;
|
||||||
|
char outbuf[RECORD_BUFSZ];
|
||||||
|
size_t outlen;
|
||||||
|
|
||||||
|
/* formatting state (format.c) */
|
||||||
|
const char *next_sep;
|
||||||
|
int depth;
|
||||||
|
struct {
|
||||||
|
const char *sep;
|
||||||
|
int name;
|
||||||
|
} depths[MAX_DEPTH];
|
||||||
|
|
||||||
|
/* ioctl state (ioctl.c) */
|
||||||
|
int ioctl_index;
|
||||||
|
unsigned int ioctl_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Trace flags. */
|
||||||
|
#define TF_INCALL 0x01 /* the process has entered a system call */
|
||||||
|
#define TF_SKIP 0x02 /* the system call result is to be skipped */
|
||||||
|
#define TF_CTX_SKIP 0x04 /* skip call result only if context changes */
|
||||||
|
#define TF_STOPPING 0x08 /* the process is expecting a SIGSTOP */
|
||||||
|
#define TF_ATTACH 0x10 /* we have not started this process */
|
||||||
|
#define TF_DETACH 0x20 /* detach from the process as soon as we can */
|
||||||
|
#define TF_EXEC 0x40 /* the process may be performing an execve() */
|
||||||
|
#define TF_NOCALL 0x80 /* no system call seen yet (for info only) */
|
||||||
|
|
||||||
|
/* Trace classes, determining how the tracer engine should handle a call. */
|
||||||
|
#define TC_NORMAL 0 /* normal call, no exceptions required */
|
||||||
|
#define TC_EXEC 1 /* exec call, success on subsequent SIGSTOP */
|
||||||
|
#define TC_SIGRET 2 /* sigreturn call, success on context change */
|
||||||
|
|
||||||
|
/* Call flags. */
|
||||||
|
#define CF_DONE 0x01 /* printing the call parameters is done */
|
||||||
|
#define CF_NORETURN 0x02 /* the call does not return on success */
|
||||||
|
#define CF_HIDE 0x04 /* do not print the current call */
|
||||||
|
#define CF_IPC_ERR 0x08 /* a failure occurred at the IPC level */
|
||||||
|
#define CF_REG_ERR 0x10 /* unable to retrieve the result register */
|
||||||
|
#define CF_MSG_ERR 0x20 /* unable to copy in the reply message */
|
||||||
|
|
||||||
|
/* Call types, determining how much has been printed up to the call split. */
|
||||||
|
#define CT_NOTDONE (0) /* not all parameters have been printed yet */
|
||||||
|
#define CT_DONE (CF_DONE) /* all parameters have been printed */
|
||||||
|
#define CT_NORETURN (CF_DONE | CF_NORETURN) /* the no-return call type */
|
||||||
|
|
||||||
|
/* Put flags. */
|
||||||
|
#define PF_FAILED 0x01 /* call failed, results may be invalid */
|
||||||
|
#define PF_LOCADDR 0x02 /* pointer is into local address space */
|
||||||
|
/* Yes, PF_LOCAL would conflict with the packet family definition. Bah. */
|
||||||
|
#define PF_ALT 0x04 /* alternative output (callee specific) */
|
||||||
|
#define PF_STRING PF_ALT /* buffer is string (put_buf only) */
|
||||||
|
#define PF_FULL 0x08 /* print full format (callee specific) */
|
||||||
|
#define PF_PATH (PF_STRING | PF_FULL) /* flags for path names */
|
||||||
|
#define PF_NONAME 0x10 /* default to no field names at this depth */
|
||||||
|
|
||||||
|
/* I/O control flags. */
|
||||||
|
#define IF_OUT 0x1 /* call to print outgoing (written) data */
|
||||||
|
#define IF_IN 0x2 /* call to print incoming (read) data */
|
||||||
|
#define IF_ALL 0x4 /* all fields printed (not really a bit) */
|
130
minix/usr.bin/trace/proto.h
Normal file
130
minix/usr.bin/trace/proto.h
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
|
||||||
|
/* call.c */
|
||||||
|
void put_endpoint(struct trace_proc *proc, const char *name, endpoint_t endpt);
|
||||||
|
void put_equals(struct trace_proc *proc);
|
||||||
|
void put_result(struct trace_proc *proc);
|
||||||
|
int default_out(struct trace_proc *proc, const message *m_out);
|
||||||
|
void default_in(struct trace_proc *proc, const message *m_out,
|
||||||
|
const message *m_in, int failed);
|
||||||
|
int call_enter(struct trace_proc *proc, int show_stack);
|
||||||
|
void call_leave(struct trace_proc *proc, int skip);
|
||||||
|
void call_replay(struct trace_proc *proc);
|
||||||
|
const char *call_name(struct trace_proc *proc);
|
||||||
|
|
||||||
|
/* error.c */
|
||||||
|
const char *get_error_name(int err);
|
||||||
|
|
||||||
|
/* escape.c */
|
||||||
|
const char *get_escape(char c);
|
||||||
|
|
||||||
|
/* format.c */
|
||||||
|
void format_reset(struct trace_proc *proc);
|
||||||
|
void format_set_sep(struct trace_proc *proc, const char *sep);
|
||||||
|
void format_push_sep(struct trace_proc *proc);
|
||||||
|
void put_field(struct trace_proc *proc, const char *name, const char *text);
|
||||||
|
void put_open(struct trace_proc *proc, const char *name, int flags,
|
||||||
|
const char *string, const char *separator);
|
||||||
|
void put_close(struct trace_proc *proc, const char *string);
|
||||||
|
void put_fmt(struct trace_proc *proc, const char *fmt, ...)
|
||||||
|
__attribute__((__format__(__printf__, 2, 3)));
|
||||||
|
void put_value(struct trace_proc *proc, const char *name, const char *fmt, ...)
|
||||||
|
__attribute__((__format__(__printf__, 3, 4)));
|
||||||
|
int put_open_struct(struct trace_proc *proc, const char *name, int flags,
|
||||||
|
vir_bytes addr, void *ptr, size_t size);
|
||||||
|
void put_close_struct(struct trace_proc *proc, int all);
|
||||||
|
void put_ptr(struct trace_proc *proc, const char *name, vir_bytes addr);
|
||||||
|
void put_buf(struct trace_proc *proc, const char *name, int flags,
|
||||||
|
vir_bytes addr, ssize_t size);
|
||||||
|
void put_flags(struct trace_proc *proc, const char *name,
|
||||||
|
const struct flags *fp, unsigned int num, const char *fmt,
|
||||||
|
unsigned int value);
|
||||||
|
void put_tail(struct trace_proc * proc, unsigned int count,
|
||||||
|
unsigned int printed);
|
||||||
|
|
||||||
|
/* ioctl.c */
|
||||||
|
void put_ioctl_req(struct trace_proc *proc, const char *name,
|
||||||
|
unsigned long req, int is_svrctl);
|
||||||
|
int put_ioctl_arg_out(struct trace_proc *proc, const char *name,
|
||||||
|
unsigned long req, vir_bytes addr, int is_svrctl);
|
||||||
|
void put_ioctl_arg_in(struct trace_proc *proc, const char *name, int failed,
|
||||||
|
unsigned long req, vir_bytes addr, int is_svrctl);
|
||||||
|
|
||||||
|
/* kernel.c */
|
||||||
|
int kernel_check(pid_t pid);
|
||||||
|
int kernel_get_name(pid_t pid, char *name, size_t size);
|
||||||
|
int kernel_is_service(pid_t pid);
|
||||||
|
int kernel_get_syscall(pid_t pid, reg_t reg[3]);
|
||||||
|
int kernel_get_retreg(pid_t pid, reg_t *retreg);
|
||||||
|
vir_bytes kernel_get_stacktop(void);
|
||||||
|
int kernel_get_context(pid_t pid, reg_t *pc, reg_t *sp, reg_t *fp);
|
||||||
|
void kernel_put_stacktrace(struct trace_proc * proc);
|
||||||
|
|
||||||
|
/* mem.c */
|
||||||
|
int mem_get_data(pid_t pid, vir_bytes addr, void *ptr, size_t len);
|
||||||
|
int mem_get_user(pid_t pid, vir_bytes addr, void *ptr, size_t len);
|
||||||
|
|
||||||
|
/* pm.c */
|
||||||
|
void put_struct_timeval(struct trace_proc *proc, const char *name, int flags,
|
||||||
|
vir_bytes addr);
|
||||||
|
void put_time(struct trace_proc *proc, const char *name, time_t time);
|
||||||
|
void put_groups(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr, int count);
|
||||||
|
|
||||||
|
/* output.c */
|
||||||
|
int output_init(const char *file);
|
||||||
|
int output_error(void);
|
||||||
|
void output_flush(void);
|
||||||
|
void record_start(struct trace_proc *proc);
|
||||||
|
void record_stop(struct trace_proc *proc);
|
||||||
|
void record_clear(struct trace_proc *proc);
|
||||||
|
int record_replay(struct trace_proc *proc);
|
||||||
|
void put_newline(void);
|
||||||
|
void put_text(struct trace_proc *proc, const char *text);
|
||||||
|
void put_space(struct trace_proc *proc);
|
||||||
|
void put_align(struct trace_proc *proc);
|
||||||
|
|
||||||
|
/* proc.c */
|
||||||
|
void proc_init(void);
|
||||||
|
struct trace_proc *proc_add(pid_t pid);
|
||||||
|
struct trace_proc *proc_get(pid_t pid);
|
||||||
|
void proc_del(struct trace_proc *proc);
|
||||||
|
struct trace_proc *proc_next(struct trace_proc *last);
|
||||||
|
unsigned int proc_count(void);
|
||||||
|
|
||||||
|
/* signal.c */
|
||||||
|
const char *get_signal_name(int sig);
|
||||||
|
|
||||||
|
/* trace.c */
|
||||||
|
extern int allnames;
|
||||||
|
extern unsigned int verbose;
|
||||||
|
extern unsigned int valuesonly;
|
||||||
|
|
||||||
|
/* vfs.c */
|
||||||
|
void put_fd(struct trace_proc *proc, const char *name, int fd);
|
||||||
|
void put_dev(struct trace_proc *proc, const char *name, dev_t dev);
|
||||||
|
|
||||||
|
/* service */
|
||||||
|
const struct calls pm_calls;
|
||||||
|
const struct calls vfs_calls;
|
||||||
|
const struct calls rs_calls;
|
||||||
|
const struct calls vm_calls;
|
||||||
|
const struct calls ipc_calls;
|
||||||
|
|
||||||
|
/* ioctl/block.c */
|
||||||
|
const char *block_ioctl_name(unsigned long req);
|
||||||
|
int block_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
|
||||||
|
int dir);
|
||||||
|
|
||||||
|
/* ioctl/char.c */
|
||||||
|
const char *char_ioctl_name(unsigned long req);
|
||||||
|
int char_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
|
||||||
|
int dir);
|
||||||
|
|
||||||
|
/* ioctl/net.c */
|
||||||
|
const char *net_ioctl_name(unsigned long req);
|
||||||
|
int net_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
|
||||||
|
int dir);
|
||||||
|
|
||||||
|
/* ioctl/svrctl.c */
|
||||||
|
const char *svrctl_name(unsigned long req);
|
||||||
|
int svrctl_arg(struct trace_proc *proc, unsigned long req, void *ptr, int dir);
|
445
minix/usr.bin/trace/service/ipc.c
Normal file
445
minix/usr.bin/trace/service/ipc.c
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
/* This file is concerned with the IPC server, not with kernel-level IPC. */
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
#include <sys/sem.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_key(struct trace_proc * proc, const char * name, key_t key)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!valuesonly && key == IPC_PRIVATE)
|
||||||
|
put_field(proc, name, "IPC_PRIVATE");
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%ld", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags ipcget_flags[] = {
|
||||||
|
FLAG(IPC_CREAT),
|
||||||
|
FLAG(IPC_EXCL),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_shmget_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_key(proc, "key", m_out->m_lc_ipc_shmget.key);
|
||||||
|
put_value(proc, "size", "%zu", m_out->m_lc_ipc_shmget.size);
|
||||||
|
put_flags(proc, "shmflg", ipcget_flags, COUNT(ipcget_flags), "0%o",
|
||||||
|
m_out->m_lc_ipc_shmget.flag);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipc_shmget_in(struct trace_proc * proc, const message * __unused m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
put_value(proc, NULL, "%d", m_in->m_lc_ipc_shmget.retid);
|
||||||
|
else
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags shmat_flags[] = {
|
||||||
|
FLAG(SHM_RDONLY),
|
||||||
|
FLAG(SHM_RND),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_shmat_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_value(proc, "shmid", "%d", m_out->m_lc_ipc_shmat.id);
|
||||||
|
put_ptr(proc, "shmaddr", (vir_bytes)m_out->m_lc_ipc_shmat.addr);
|
||||||
|
put_flags(proc, "shmflg", shmat_flags, COUNT(shmat_flags), "0x%x",
|
||||||
|
m_out->m_lc_ipc_shmat.flag);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipc_shmat_in(struct trace_proc * proc, const message * __unused m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
put_ptr(proc, NULL, (vir_bytes)m_in->m_lc_ipc_shmat.retaddr);
|
||||||
|
else
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_shmdt_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_ptr(proc, "shmaddr", (vir_bytes)m_out->m_lc_ipc_shmdt.addr);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_shmctl_cmd(struct trace_proc * proc, const char * name, int cmd)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (cmd) {
|
||||||
|
TEXT(IPC_RMID);
|
||||||
|
TEXT(IPC_SET);
|
||||||
|
TEXT(IPC_STAT);
|
||||||
|
TEXT(SHM_STAT);
|
||||||
|
TEXT(SHM_INFO);
|
||||||
|
TEXT(IPC_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags shm_mode_flags[] = {
|
||||||
|
FLAG(SHM_DEST),
|
||||||
|
FLAG(SHM_LOCKED),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_struct_shmid_ds(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr)
|
||||||
|
{
|
||||||
|
struct shmid_ds buf;
|
||||||
|
int set;
|
||||||
|
|
||||||
|
if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Is this an IPC_SET call? Then print a small subset of fields.. */
|
||||||
|
set = (flags & PF_ALT);
|
||||||
|
|
||||||
|
put_open(proc, "shm_perm", 0, "{", ", ");
|
||||||
|
|
||||||
|
put_value(proc, "uid", "%u", buf.shm_perm.uid);
|
||||||
|
put_value(proc, "gid", "%u", buf.shm_perm.gid);
|
||||||
|
if (!set && verbose > 0) {
|
||||||
|
put_value(proc, "cuid", "%u", buf.shm_perm.cuid);
|
||||||
|
put_value(proc, "cgid", "%u", buf.shm_perm.cgid);
|
||||||
|
}
|
||||||
|
put_flags(proc, "mode", shm_mode_flags, COUNT(shm_mode_flags),
|
||||||
|
"0%03o", buf.shm_perm.mode);
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
|
||||||
|
if (!set) {
|
||||||
|
put_value(proc, "shm_segsz", "%zu", buf.shm_segsz);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "shm_lpid", "%d", buf.shm_lpid);
|
||||||
|
put_value(proc, "shm_cpid", "%d", buf.shm_cpid);
|
||||||
|
put_time(proc, "shm_atime", buf.shm_atime);
|
||||||
|
put_time(proc, "shm_dtime", buf.shm_dtime);
|
||||||
|
put_time(proc, "shm_ctime", buf.shm_ctime);
|
||||||
|
}
|
||||||
|
put_value(proc, "shm_nattch", "%u", buf.shm_nattch);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, set || verbose > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_shmctl_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_value(proc, "shmid", "%d", m_out->m_lc_ipc_shmctl.id);
|
||||||
|
put_shmctl_cmd(proc, "cmd", m_out->m_lc_ipc_shmctl.cmd);
|
||||||
|
|
||||||
|
/* TODO: add support for the IPC_INFO and SHM_INFO structures.. */
|
||||||
|
switch (m_out->m_lc_ipc_shmctl.cmd) {
|
||||||
|
case IPC_STAT:
|
||||||
|
case SHM_STAT:
|
||||||
|
return CT_NOTDONE;
|
||||||
|
|
||||||
|
case IPC_SET:
|
||||||
|
put_struct_shmid_ds(proc, "buf", PF_ALT,
|
||||||
|
(vir_bytes)m_out->m_lc_ipc_shmctl.buf);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
put_ptr(proc, "buf", (vir_bytes)m_out->m_lc_ipc_shmctl.buf);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipc_shmctl_in(struct trace_proc * proc, const message * m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (m_out->m_lc_ipc_shmctl.cmd) {
|
||||||
|
case IPC_STAT:
|
||||||
|
case SHM_STAT:
|
||||||
|
put_struct_shmid_ds(proc, "buf", failed,
|
||||||
|
(vir_bytes)m_out->m_lc_ipc_shmctl.buf);
|
||||||
|
put_equals(proc);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failed) {
|
||||||
|
switch (m_out->m_lc_ipc_shmctl.cmd) {
|
||||||
|
case SHM_INFO:
|
||||||
|
case SHM_STAT:
|
||||||
|
case IPC_INFO:
|
||||||
|
put_value(proc, NULL, "%d", m_in->m_lc_ipc_shmctl.ret);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_semget_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_key(proc, "key", m_out->m_lc_ipc_semget.key);
|
||||||
|
put_value(proc, "nsems", "%d", m_out->m_lc_ipc_semget.nr);
|
||||||
|
put_flags(proc, "semflg", ipcget_flags, COUNT(ipcget_flags), "0%o",
|
||||||
|
m_out->m_lc_ipc_semget.flag);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipc_semget_in(struct trace_proc * proc, const message * __unused m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
put_value(proc, NULL, "%d", m_in->m_lc_ipc_semget.retid);
|
||||||
|
else
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_semctl_cmd(struct trace_proc * proc, const char * name, int cmd)
|
||||||
|
{
|
||||||
|
const char *text = NULL;
|
||||||
|
|
||||||
|
if (!valuesonly) {
|
||||||
|
switch (cmd) {
|
||||||
|
TEXT(IPC_RMID);
|
||||||
|
TEXT(IPC_SET);
|
||||||
|
TEXT(IPC_STAT);
|
||||||
|
TEXT(GETNCNT);
|
||||||
|
TEXT(GETPID);
|
||||||
|
TEXT(GETVAL);
|
||||||
|
TEXT(GETALL);
|
||||||
|
TEXT(GETZCNT);
|
||||||
|
TEXT(SETVAL);
|
||||||
|
TEXT(SETALL);
|
||||||
|
TEXT(SEM_STAT);
|
||||||
|
TEXT(SEM_INFO);
|
||||||
|
TEXT(IPC_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != NULL)
|
||||||
|
put_field(proc, name, text);
|
||||||
|
else
|
||||||
|
put_value(proc, name, "%d", cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_struct_semid_ds(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr)
|
||||||
|
{
|
||||||
|
struct semid_ds buf;
|
||||||
|
int set;
|
||||||
|
|
||||||
|
if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Is this an IPC_SET call? Then print a small subset of fields.. */
|
||||||
|
set = (flags & PF_ALT);
|
||||||
|
|
||||||
|
put_open(proc, "sem_perm", 0, "{", ", ");
|
||||||
|
|
||||||
|
put_value(proc, "uid", "%u", buf.sem_perm.uid);
|
||||||
|
put_value(proc, "gid", "%u", buf.sem_perm.gid);
|
||||||
|
if (!set && verbose > 0) {
|
||||||
|
put_value(proc, "cuid", "%u", buf.sem_perm.cuid);
|
||||||
|
put_value(proc, "cgid", "%u", buf.sem_perm.cgid);
|
||||||
|
}
|
||||||
|
put_value(proc, "mode", "0%03o", buf.sem_perm.mode);
|
||||||
|
|
||||||
|
put_close(proc, "}");
|
||||||
|
|
||||||
|
if (!set) {
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_time(proc, "sem_otime", buf.sem_otime);
|
||||||
|
put_time(proc, "sem_ctime", buf.sem_ctime);
|
||||||
|
}
|
||||||
|
put_value(proc, "sem_nsems", "%u", buf.sem_nsems);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, set || verbose > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_semctl_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_value(proc, "semid", "%d", m_out->m_lc_ipc_semctl.id);
|
||||||
|
put_value(proc, "semnum", "%d", m_out->m_lc_ipc_semctl.num);
|
||||||
|
put_semctl_cmd(proc, "cmd", m_out->m_lc_ipc_semctl.cmd);
|
||||||
|
|
||||||
|
/* TODO: add support for the IPC_INFO and SEM_INFO structures.. */
|
||||||
|
switch (m_out->m_lc_ipc_semctl.cmd) {
|
||||||
|
case IPC_STAT:
|
||||||
|
case SEM_STAT:
|
||||||
|
return CT_NOTDONE;
|
||||||
|
|
||||||
|
case IPC_SET:
|
||||||
|
put_struct_semid_ds(proc, "buf", PF_ALT,
|
||||||
|
(vir_bytes)m_out->m_lc_ipc_semctl.opt);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
|
||||||
|
case IPC_INFO:
|
||||||
|
case SEM_INFO:
|
||||||
|
put_ptr(proc, "buf", (vir_bytes)m_out->m_lc_ipc_semctl.opt);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
|
||||||
|
case GETALL:
|
||||||
|
case SETALL:
|
||||||
|
put_ptr(proc, "array", (vir_bytes)m_out->m_lc_ipc_semctl.opt);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
|
||||||
|
case SETVAL:
|
||||||
|
put_value(proc, "val", "%d", m_out->m_lc_ipc_semctl.opt);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipc_semctl_in(struct trace_proc * proc, const message * m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (m_out->m_lc_ipc_semctl.cmd) {
|
||||||
|
case IPC_STAT:
|
||||||
|
case SEM_STAT:
|
||||||
|
put_struct_semid_ds(proc, "buf", failed,
|
||||||
|
(vir_bytes)m_out->m_lc_ipc_semctl.opt);
|
||||||
|
put_equals(proc);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failed) {
|
||||||
|
switch (m_out->m_lc_ipc_semctl.cmd) {
|
||||||
|
case GETNCNT:
|
||||||
|
case GETPID:
|
||||||
|
case GETVAL:
|
||||||
|
case GETZCNT:
|
||||||
|
case SEM_INFO:
|
||||||
|
case SEM_STAT:
|
||||||
|
case IPC_INFO:
|
||||||
|
put_value(proc, NULL, "%d", m_in->m_lc_ipc_semctl.ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags sem_flags[] = {
|
||||||
|
FLAG(IPC_NOWAIT),
|
||||||
|
FLAG(SEM_UNDO),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_struct_sembuf(struct trace_proc * proc, const char * name, int flags,
|
||||||
|
vir_bytes addr)
|
||||||
|
{
|
||||||
|
struct sembuf buf;
|
||||||
|
int all;
|
||||||
|
|
||||||
|
if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
all = FALSE;
|
||||||
|
put_value(proc, "sem_num", "%u", buf.sem_num);
|
||||||
|
put_value(proc, "sem_op", "%d", buf.sem_op);
|
||||||
|
if (verbose > 0 || (buf.sem_flg & ~SEM_UNDO) != 0) {
|
||||||
|
put_flags(proc, "sem_flg", sem_flags, COUNT(sem_flags), "0x%x",
|
||||||
|
buf.sem_flg);
|
||||||
|
all = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_sembuf_array(struct trace_proc * proc, const char * name, vir_bytes addr,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
struct sembuf buf[SEMOPM]; /* about 600 bytes, so OK for the stack */
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (valuesonly > 1 || count > SEMOPM ||
|
||||||
|
mem_get_data(proc->pid, addr, &buf, count * sizeof(buf[0])) != 0) {
|
||||||
|
put_ptr(proc, name, addr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_open(proc, name, PF_NONAME, "[", ", ");
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
put_struct_sembuf(proc, NULL, PF_LOCADDR, (vir_bytes)&buf[i]);
|
||||||
|
put_close(proc, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ipc_semop_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_value(proc, "semid", "%d", m_out->m_lc_ipc_semop.id);
|
||||||
|
put_sembuf_array(proc, "sops", (vir_bytes)m_out->m_lc_ipc_semop.ops,
|
||||||
|
m_out->m_lc_ipc_semop.size);
|
||||||
|
put_value(proc, "nsops", "%zu", m_out->m_lc_ipc_semop.size);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IPC_CALL(c) [((IPC_ ## c) - IPC_BASE)]
|
||||||
|
|
||||||
|
static const struct call_handler ipc_map[] = {
|
||||||
|
IPC_CALL(SHMGET) = HANDLER("shmget", ipc_shmget_out, ipc_shmget_in),
|
||||||
|
IPC_CALL(SHMAT) = HANDLER("shmat", ipc_shmat_out, ipc_shmat_in),
|
||||||
|
IPC_CALL(SHMDT) = HANDLER("shmdt", ipc_shmdt_out, default_in),
|
||||||
|
IPC_CALL(SHMCTL) = HANDLER("shmctl", ipc_shmctl_out, ipc_shmctl_in),
|
||||||
|
IPC_CALL(SEMGET) = HANDLER("semget", ipc_semget_out, ipc_semget_in),
|
||||||
|
IPC_CALL(SEMCTL) = HANDLER("semctl", ipc_semctl_out, ipc_semctl_in),
|
||||||
|
IPC_CALL(SEMOP) = HANDLER("semop", ipc_semop_out, default_in),
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct calls ipc_calls = {
|
||||||
|
.endpt = ANY,
|
||||||
|
.base = IPC_BASE,
|
||||||
|
.map = ipc_map,
|
||||||
|
.count = COUNT(ipc_map)
|
||||||
|
};
|
1396
minix/usr.bin/trace/service/pm.c
Normal file
1396
minix/usr.bin/trace/service/pm.c
Normal file
File diff suppressed because it is too large
Load diff
140
minix/usr.bin/trace/service/rs.c
Normal file
140
minix/usr.bin/trace/service/rs.c
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <minix/rs.h>
|
||||||
|
|
||||||
|
static const struct flags rss_flags[] = {
|
||||||
|
FLAG(RSS_COPY),
|
||||||
|
FLAG(RSS_REUSE),
|
||||||
|
FLAG(RSS_NOBLOCK),
|
||||||
|
FLAG(RSS_REPLICA),
|
||||||
|
FLAG(RSS_SELF_LU),
|
||||||
|
FLAG(RSS_SYS_BASIC_CALLS),
|
||||||
|
FLAG(RSS_VM_BASIC_CALLS),
|
||||||
|
FLAG(RSS_NO_BIN_EXP),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
put_struct_rs_start(struct trace_proc * proc, const char * name,
|
||||||
|
vir_bytes addr)
|
||||||
|
{
|
||||||
|
struct rs_start buf;
|
||||||
|
|
||||||
|
if (!put_open_struct(proc, name, 0, addr, &buf, sizeof(buf)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (verbose > 0)
|
||||||
|
put_flags(proc, "rss_flags", rss_flags, COUNT(rss_flags),
|
||||||
|
"0x%x", buf.rss_flags);
|
||||||
|
put_buf(proc, "rss_cmd", 0, (vir_bytes)buf.rss_cmd, buf.rss_cmdlen);
|
||||||
|
put_buf(proc, "rss_label", 0, (vir_bytes)buf.rss_label.l_addr,
|
||||||
|
buf.rss_label.l_len);
|
||||||
|
if (verbose > 0 || buf.rss_major != 0)
|
||||||
|
put_value(proc, "rss_major", "%d", buf.rss_major);
|
||||||
|
if (verbose > 0 || buf.devman_id != 0)
|
||||||
|
put_value(proc, "devman_id", "%d", buf.devman_id);
|
||||||
|
put_value(proc, "rss_uid", "%u", buf.rss_uid);
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_endpoint(proc, "rss_sigmgr", buf.rss_sigmgr);
|
||||||
|
put_endpoint(proc, "rss_scheduler", buf.rss_sigmgr);
|
||||||
|
}
|
||||||
|
if (verbose > 1) {
|
||||||
|
put_value(proc, "rss_priority", "%d", buf.rss_priority);
|
||||||
|
put_value(proc, "rss_quantum", "%d", buf.rss_quantum);
|
||||||
|
}
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "rss_period", "%ld", buf.rss_period);
|
||||||
|
put_buf(proc, "rss_script", 0, (vir_bytes)buf.rss_script,
|
||||||
|
buf.rss_scriptlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, FALSE /*all*/); /* TODO: the remaining fields */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function is shared between rs_up and rs_edit. */
|
||||||
|
static int
|
||||||
|
rs_up_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_struct_rs_start(proc, "addr", (vir_bytes)m_out->m_rs_req.addr);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is shared between rs_down, rs_refresh, rs_restart, and
|
||||||
|
* rs_clone.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
rs_label_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We are not using PF_STRING here, because unlike in most places
|
||||||
|
* (including rs_lookup), the string length does not include the
|
||||||
|
* terminating NULL character.
|
||||||
|
*/
|
||||||
|
put_buf(proc, "label", 0, (vir_bytes)m_out->m_rs_req.addr,
|
||||||
|
m_out->m_rs_req.len);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rs_update_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: this is a value from the wrong message union, and that is
|
||||||
|
* actually a minix bug.
|
||||||
|
*/
|
||||||
|
put_struct_rs_start(proc, "addr", (vir_bytes)m_out->m_rs_req.addr);
|
||||||
|
|
||||||
|
/* TODO: interpret these fields */
|
||||||
|
put_value(proc, "state", "%d", m_out->m_rs_update.state);
|
||||||
|
put_value(proc, "maxtime", "%d", m_out->m_rs_update.prepare_maxtime);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rs_lookup_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_buf(proc, "label", PF_STRING, (vir_bytes)m_out->m_rs_req.name,
|
||||||
|
m_out->m_rs_req.name_len);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rs_lookup_in(struct trace_proc * proc, const message * __unused m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
put_endpoint(proc, NULL, m_in->m_rs_req.endpoint);
|
||||||
|
else
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define RS_CALL(c) [((RS_ ## c) - RS_RQ_BASE)]
|
||||||
|
|
||||||
|
static const struct call_handler rs_map[] = {
|
||||||
|
RS_CALL(UP) = HANDLER("rs_up", rs_up_out, default_in),
|
||||||
|
RS_CALL(DOWN) = HANDLER("rs_down", rs_label_out, default_in),
|
||||||
|
RS_CALL(REFRESH) = HANDLER("rs_refresh", rs_label_out, default_in),
|
||||||
|
RS_CALL(RESTART) = HANDLER("rs_restart", rs_label_out, default_in),
|
||||||
|
RS_CALL(SHUTDOWN) = HANDLER("rs_shutdown", default_out, default_in),
|
||||||
|
RS_CALL(CLONE) = HANDLER("rs_clone", rs_label_out, default_in),
|
||||||
|
RS_CALL(UPDATE) = HANDLER("rs_update", rs_update_out, default_in),
|
||||||
|
RS_CALL(EDIT) = HANDLER("rs_edit", rs_up_out, default_in),
|
||||||
|
RS_CALL(LOOKUP) = HANDLER("rs_lookup", rs_lookup_out, rs_lookup_in),
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct calls rs_calls = {
|
||||||
|
.endpt = RS_PROC_NR,
|
||||||
|
.base = RS_RQ_BASE,
|
||||||
|
.map = rs_map,
|
||||||
|
.count = COUNT(rs_map)
|
||||||
|
};
|
1457
minix/usr.bin/trace/service/vfs.c
Normal file
1457
minix/usr.bin/trace/service/vfs.c
Normal file
File diff suppressed because it is too large
Load diff
135
minix/usr.bin/trace/service/vm.c
Normal file
135
minix/usr.bin/trace/service/vm.c
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
vm_brk_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_ptr(proc, "addr", (vir_bytes)m_out->m_lc_vm_brk.addr);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct flags mmap_prot[] = {
|
||||||
|
FLAG_ZERO(PROT_NONE),
|
||||||
|
FLAG(PROT_READ),
|
||||||
|
FLAG(PROT_WRITE),
|
||||||
|
FLAG(PROT_EXEC),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct flags mmap_flags[] = {
|
||||||
|
FLAG(MAP_SHARED),
|
||||||
|
FLAG(MAP_PRIVATE),
|
||||||
|
FLAG(MAP_FIXED),
|
||||||
|
FLAG(MAP_RENAME),
|
||||||
|
FLAG(MAP_NORESERVE),
|
||||||
|
FLAG(MAP_INHERIT),
|
||||||
|
FLAG(MAP_HASSEMAPHORE),
|
||||||
|
FLAG(MAP_TRYFIXED),
|
||||||
|
FLAG(MAP_WIRED),
|
||||||
|
FLAG_MASK(MAP_ANON | MAP_STACK, MAP_FILE),
|
||||||
|
FLAG(MAP_ANON),
|
||||||
|
FLAG(MAP_STACK),
|
||||||
|
FLAG(MAP_UNINITIALIZED),
|
||||||
|
FLAG(MAP_PREALLOC),
|
||||||
|
FLAG(MAP_CONTIG),
|
||||||
|
FLAG(MAP_LOWER16M),
|
||||||
|
FLAG(MAP_LOWER1M),
|
||||||
|
FLAG(MAP_THIRDPARTY),
|
||||||
|
/* TODO: interpret alignments for which there is no constant */
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_64KB),
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_16MB),
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_4GB),
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_1TB),
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_256TB),
|
||||||
|
FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_64PB),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
vm_mmap_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (m_out->m_mmap.flags & MAP_THIRDPARTY)
|
||||||
|
put_endpoint(proc, "forwhom", m_out->m_mmap.forwhom);
|
||||||
|
put_ptr(proc, "addr", (vir_bytes)m_out->m_mmap.addr);
|
||||||
|
put_value(proc, "len", "%zu", m_out->m_mmap.len);
|
||||||
|
put_flags(proc, "prot", mmap_prot, COUNT(mmap_prot), "0x%x",
|
||||||
|
m_out->m_mmap.prot);
|
||||||
|
put_flags(proc, "flags", mmap_flags, COUNT(mmap_flags), "0x%x",
|
||||||
|
m_out->m_mmap.flags);
|
||||||
|
put_fd(proc, "fd", m_out->m_mmap.fd);
|
||||||
|
put_value(proc, "offset", "%"PRId64, m_out->m_mmap.offset);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vm_mmap_in(struct trace_proc * proc, const message * __unused m_out,
|
||||||
|
const message * m_in, int failed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
put_ptr(proc, NULL, (vir_bytes)m_in->m_mmap.retaddr);
|
||||||
|
else
|
||||||
|
/* TODO: consider printing MAP_FAILED in the right cases */
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vm_munmap_out(struct trace_proc * proc, const message * m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
put_ptr(proc, "addr", (vir_bytes)m_out->m_mmap.addr);
|
||||||
|
put_value(proc, "len", "%zu", m_out->m_mmap.len);
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vm_getrusage_out(struct trace_proc * __unused proc,
|
||||||
|
const message * __unused m_out)
|
||||||
|
{
|
||||||
|
|
||||||
|
return CT_NOTDONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vm_getrusage_in(struct trace_proc * proc, const message * m_out,
|
||||||
|
const message * __unused m_in, int failed)
|
||||||
|
{
|
||||||
|
struct rusage buf;
|
||||||
|
|
||||||
|
/* Inline; we will certainly not be reusing this anywhere else. */
|
||||||
|
if (put_open_struct(proc, "rusage", failed,
|
||||||
|
m_out->m_lc_vm_rusage.addr, &buf, sizeof(buf))) {
|
||||||
|
if (verbose > 0) {
|
||||||
|
put_value(proc, "ru_maxrss", "%ld", buf.ru_maxrss);
|
||||||
|
put_value(proc, "ru_minflt", "%ld", buf.ru_minflt);
|
||||||
|
put_value(proc, "ru_majflt", "%ld", buf.ru_majflt);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_close_struct(proc, verbose > 0);
|
||||||
|
}
|
||||||
|
put_equals(proc);
|
||||||
|
put_result(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VM_CALL(c) [((VM_ ## c) - VM_RQ_BASE)]
|
||||||
|
|
||||||
|
static const struct call_handler vm_map[] = {
|
||||||
|
VM_CALL(BRK) = HANDLER("brk", vm_brk_out, default_in),
|
||||||
|
VM_CALL(MMAP) = HANDLER("mmap", vm_mmap_out, vm_mmap_in),
|
||||||
|
VM_CALL(MUNMAP) = HANDLER("munmap", vm_munmap_out, default_in),
|
||||||
|
VM_CALL(GETRUSAGE) = HANDLER("vm_getrusage", vm_getrusage_out,
|
||||||
|
vm_getrusage_in),
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct calls vm_calls = {
|
||||||
|
.endpt = VM_PROC_NR,
|
||||||
|
.base = VM_RQ_BASE,
|
||||||
|
.map = vm_map,
|
||||||
|
.count = COUNT(vm_map)
|
||||||
|
};
|
32
minix/usr.bin/trace/signal.awk
Normal file
32
minix/usr.bin/trace/signal.awk
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# This one is a bit trickier than error.awk, because sys/signal.h is not as
|
||||||
|
# easy to parse. We currently assume that all (userland) signals are listed
|
||||||
|
# before the first reference to "_KERNEL", and anything else that looks like a
|
||||||
|
# signal definition (but isn't) is after that first reference.
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
printf("/* This file is automatically generated by signal.awk */\n\n");
|
||||||
|
printf("#include \"inc.h\"\n\n");
|
||||||
|
printf("static const char *const signals[] = {\n");
|
||||||
|
}
|
||||||
|
/^#define/ {
|
||||||
|
name = $2;
|
||||||
|
if (!match(name, "SIG[^_]"))
|
||||||
|
next;
|
||||||
|
number = $3;
|
||||||
|
if (number < 0 || number == "SIGABRT")
|
||||||
|
next;
|
||||||
|
printf("\t[%s] = \"%s\",\n", name, name);
|
||||||
|
}
|
||||||
|
/_KERNEL/ {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
printf("};\n\n");
|
||||||
|
printf("const char *\nget_signal_name(int sig)\n{\n\n");
|
||||||
|
printf("\tif (sig >= 0 && sig < sizeof(signals) / sizeof(signals[0]) &&\n");
|
||||||
|
printf("\t signals[sig] != NULL)\n");
|
||||||
|
printf("\t\treturn signals[sig];\n");
|
||||||
|
printf("\telse\n");
|
||||||
|
printf("\t\treturn NULL;\n");
|
||||||
|
printf("}\n");
|
||||||
|
}
|
334
minix/usr.bin/trace/trace.1
Normal file
334
minix/usr.bin/trace/trace.1
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
.Dd November 2, 2014
|
||||||
|
.Dt TRACE 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm trace
|
||||||
|
.Nd print process system calls and signals
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Fl fgNsVv
|
||||||
|
.Op Fl o Ar file
|
||||||
|
.Op Fl p Ar pid
|
||||||
|
.Op Ar command
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
utility shows one or more processes to be traced.
|
||||||
|
For each traced process,
|
||||||
|
.Nm
|
||||||
|
prints the system calls the process makes and the signals
|
||||||
|
it receives.
|
||||||
|
The user can let
|
||||||
|
.Nm
|
||||||
|
start a
|
||||||
|
.Ar command
|
||||||
|
to be traced, and/or attach to one or more existing processes.
|
||||||
|
.Pp
|
||||||
|
The utility will run until no processes are left to trace, or until the user
|
||||||
|
presses the interrupt key (typically Ctrl-C).
|
||||||
|
Pressing this key once will cause all attached processes to be detached, with
|
||||||
|
the hope that the command that was started will also terminate cleanly from the
|
||||||
|
interruption.
|
||||||
|
Pressing the interrupt key once more kills the command that was started.
|
||||||
|
.Pp
|
||||||
|
The following options are available:
|
||||||
|
.Bl -tag -width XoXfileXX
|
||||||
|
.It Fl f
|
||||||
|
Follow forks.
|
||||||
|
Attach automatically to forked child processes.
|
||||||
|
Child processes of the started command will be treated as attached processes,
|
||||||
|
in that upon Ctrl-C presses they will be detached rather than killed.
|
||||||
|
.It Fl g
|
||||||
|
Enable call grouping.
|
||||||
|
With this option, the tracing engine tries to reduce noise from call preemption
|
||||||
|
by first polling the process that was active last.
|
||||||
|
This should reduce in cleaner output, but may also cause a single process to be
|
||||||
|
scheduled repeatedly and thus cause starvation.
|
||||||
|
.It Fl N
|
||||||
|
Print all names.
|
||||||
|
By default, the most structure fields are printed with their name.
|
||||||
|
This option enables printing of all available names, which also includes
|
||||||
|
system call parameter names.
|
||||||
|
This flag may be useful to figure out the meaning of a parameter, and for
|
||||||
|
automatic processing of the output.
|
||||||
|
.It Fl s
|
||||||
|
Print stack traces.
|
||||||
|
Each system call, and each signal arriving outside a system call, will be
|
||||||
|
preceded by a line showing the process's current stack trace.
|
||||||
|
For signals blocked by the target process, the stack trace may not be
|
||||||
|
meaningful.
|
||||||
|
Stack traces may not be supported on all platforms.
|
||||||
|
.It Fl V
|
||||||
|
Print values only.
|
||||||
|
If this flag is given once, numerical values will be printed instead of
|
||||||
|
string constants.
|
||||||
|
In addition, if it is given twice, the addresses of structures will be printed
|
||||||
|
instead of their contents.
|
||||||
|
.It Fl v
|
||||||
|
Increase verbosity.
|
||||||
|
By default, the output will be terse, in that not all structure fields are
|
||||||
|
shown, and strings and arrays are not always printed in full.
|
||||||
|
If this flag is provided once, more and longer output will be printed.
|
||||||
|
If it is provided twice, the tracer will print as much as possible.
|
||||||
|
.It Fl o Ar file
|
||||||
|
Redirect output.
|
||||||
|
By default, the output is sent to standard error.
|
||||||
|
With this option, the output is written to the given
|
||||||
|
.Ar file
|
||||||
|
instead.
|
||||||
|
.It Fl p Ar pid
|
||||||
|
Attach to a process.
|
||||||
|
This option makes
|
||||||
|
.Nm
|
||||||
|
attach to an existing process with process ID
|
||||||
|
.Ar pid .
|
||||||
|
This option may be used multiple times.
|
||||||
|
When attaching to one or more processes this way, starting a command becomes
|
||||||
|
optional.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
If the user presses the information key (typically Ctrl-T), the list of traced
|
||||||
|
process along with their current status will be printed.
|
||||||
|
.Sh OUTPUT FORMAT
|
||||||
|
System calls are printed with the following general output format:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
.Sy name Ns ( Ns Sy parameters Ns ) = Sy result
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
Other informational lines may be printed about the status of the process.
|
||||||
|
These lines typically start with an uppercase letter, while system calls
|
||||||
|
always start with a lowercase letter or an underscore.
|
||||||
|
The following example shows the tracer output for a program that prints its
|
||||||
|
own user ID:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
Tracing printuid (pid 12685)
|
||||||
|
minix_getinfo() = 0
|
||||||
|
getuid() = 0 (euid=1)
|
||||||
|
write(1, "My uid: 0\en", 10) = 10
|
||||||
|
exit(0)
|
||||||
|
Process exited normally with code 0
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
The first and last lines of the output provide status information about the
|
||||||
|
traced process.
|
||||||
|
Some calls return multiple results; extended results are printed in parentheses
|
||||||
|
after the primary call result, typically in
|
||||||
|
.Va name Ns = Ns Va value
|
||||||
|
format for clarity.
|
||||||
|
System calls that do not return on success, such as
|
||||||
|
.Fn exit ,
|
||||||
|
are printed without the equals sign and result, unless they fail.
|
||||||
|
System call failure is printed according to POSIX conventions; that is, the
|
||||||
|
call is assumed to return -1 with the value of
|
||||||
|
.Va errno
|
||||||
|
printed in square brackets after it:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
setuid(0) = -1 [EPERM]
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
If a system call ends up in an IPC-level failure, the -1 value will be preceded
|
||||||
|
by an
|
||||||
|
.Dq Li <ipc>
|
||||||
|
string.
|
||||||
|
However, this string will be omitted if the system call itself is printed at
|
||||||
|
the IPC level (that is, as an
|
||||||
|
.Fn ipc_sendrec
|
||||||
|
call), generally because
|
||||||
|
.Nm
|
||||||
|
has no handler to print the actual system call.
|
||||||
|
.Pp
|
||||||
|
Signals are printed as they arrive at the traced process, using two asterisks
|
||||||
|
on both side of the signal name.
|
||||||
|
Signals may arrive both during and outside the execution of a system call:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
read(3, ** SIGUSR1 ** &0xeffff867, 4096) = -1 [EINTR]
|
||||||
|
** SIGUSR2 **
|
||||||
|
getpid() = 5278 (ppid=5277)
|
||||||
|
kill(5278, SIGTERM) = ** SIGTERM ** <..>
|
||||||
|
Process terminated from signal SIGTERM
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
Multiple signals may be printed consecutively.
|
||||||
|
The above example illustrates a few other important aspects of output
|
||||||
|
formatting.
|
||||||
|
Some call parameters may be printed only after the system call returns, in
|
||||||
|
order to show their actual value.
|
||||||
|
For the
|
||||||
|
.Fn read
|
||||||
|
call, this would be the bytes that were read.
|
||||||
|
Upon failure, no bytes were read, so the buffer pointer is printed instead.
|
||||||
|
Finally, if a call that is expected to return (here,
|
||||||
|
.Fn kill )
|
||||||
|
does not return before the process terminates, the line ends with a
|
||||||
|
.Dq Li <..>
|
||||||
|
marker.
|
||||||
|
This is an instance of call preemption; more about that later.
|
||||||
|
.Pp
|
||||||
|
Pointers are printed with a
|
||||||
|
.Sq Li &
|
||||||
|
prefix, except for NULL, which is printed using its own name.
|
||||||
|
In general, named constants are used instead of numerical constants wherever
|
||||||
|
that makes sense.
|
||||||
|
For pointers of which the address is not available, typically because its
|
||||||
|
contents are passed by value,
|
||||||
|
.Dq Li &..
|
||||||
|
is shown instead.
|
||||||
|
.Pp
|
||||||
|
Data buffers are printed as double-quoted strings, using C-style character
|
||||||
|
escaping for nontextual bytes.
|
||||||
|
If either the verbosity level or a copy error prevents the whole data buffer
|
||||||
|
from being printed, two dots will be printed after the closing quote.
|
||||||
|
The same is done when printing a string buffer which does not have a null
|
||||||
|
termination byte within its range.
|
||||||
|
Path names are shown in full regardless of the verbosity level.
|
||||||
|
.Pp
|
||||||
|
Structures are printed as a set of structure fields enclosed in curly brackets.
|
||||||
|
The
|
||||||
|
.Va name Ns = Ns Va value
|
||||||
|
format is used, unless printing names for that structure type would introduce
|
||||||
|
too much noise and the
|
||||||
|
.Dq print all names
|
||||||
|
option is not given.
|
||||||
|
For many structures, by default only a subset of their fields are printed.
|
||||||
|
In this case, a
|
||||||
|
.Dq Li ..
|
||||||
|
entry is added at the end.
|
||||||
|
In some cases, an attempt is made to print only the most useful fields:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
stat("/etc/motd", {st_mode=S_IFREG|0755, st_size=747, ..}) = 0
|
||||||
|
stat("/dev/tty", {st_mode=S_IFCHR|0666, st_rdev=<5,0>, ..}) = 0
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
As shown in the above example, flag fields are printed as a combination of
|
||||||
|
named constants, separated by a
|
||||||
|
.Sq Li |
|
||||||
|
pipe symbol.
|
||||||
|
Any leftover numerical bits are printed at the end.
|
||||||
|
The example also shows the format in which major/minor pairs are printed for
|
||||||
|
device numbers.
|
||||||
|
This is a custom format; there are a few other custom formats throughout the
|
||||||
|
.Nm
|
||||||
|
output which are supposed to be sufficiently self-explanatory (and rare).
|
||||||
|
.Pp
|
||||||
|
Arrays are printed using square brackets.
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
pipe2([3, 4], 0) = 0
|
||||||
|
getdents(3, [..(45)], 4096) = 1824
|
||||||
|
getdents(3, [{d_name="."}, ..(+44)], 4096) = 1824
|
||||||
|
getdents(3, [], 4096) = 0
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
If the array contents are not printed as per the settings for the verbosity
|
||||||
|
level, a single pseudo-element shows how many actual elements were in the array
|
||||||
|
(the second line in the example).
|
||||||
|
If the number of printed elements is limited, a final pseudo-element shows how
|
||||||
|
many additional elements were not printed (the third line in the example).
|
||||||
|
If a copy error occurs while part of the array has been printed already, a
|
||||||
|
last
|
||||||
|
.Dq Li ..(?)
|
||||||
|
pseudo-element is printed; for immediate failure, the array's pointer is shown.
|
||||||
|
Empty arrays will be printed as
|
||||||
|
.Dq Li [] .
|
||||||
|
.Pp
|
||||||
|
Bit sets are printed as arrays except with just a space and no comma as
|
||||||
|
bit separator, closely following the output format of
|
||||||
|
.Nm Ns 's
|
||||||
|
original inspiration
|
||||||
|
.Sy strace .
|
||||||
|
For signal sets in particular, an inverted bit set may be shown, thus printing
|
||||||
|
only the bits which are not set; such sets are prefixed with a
|
||||||
|
.Sq Li ~
|
||||||
|
to the opening bracket:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
sigprocmask(SIG_SETMASK, ~[USR1 USR2], []) = 0
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
Note how the
|
||||||
|
.Dq Li SIG
|
||||||
|
prefixes are omitted for brevity in this case.
|
||||||
|
.Pp
|
||||||
|
When multiple processes are traced at once, each line will have a prefix that
|
||||||
|
shows the PID of the corresponding process.
|
||||||
|
When the number of processes drops to one again, one more line is prefixed with
|
||||||
|
the PID of the remaining process, but using a
|
||||||
|
.Sq Li '
|
||||||
|
instead of a
|
||||||
|
.Sq Li |
|
||||||
|
symbol:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
fork() = 25813
|
||||||
|
25813| Tracing test*F (pid 25813)
|
||||||
|
25813| fork() = 0
|
||||||
|
25812| waitpid(-1, &.., WNOHANG) = 0
|
||||||
|
25813| exit(1)
|
||||||
|
25813| Process exited normally with code 1
|
||||||
|
25812' waitpid(-1, W_EXITED(1), WNOHANG) = 25813
|
||||||
|
exit(0)
|
||||||
|
Process exited normally with code 0
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
If a process is preempted while making a system call, the system call will
|
||||||
|
be shown as suspended with the
|
||||||
|
.Dq Li <..>
|
||||||
|
suffix.
|
||||||
|
Later, when the system call is resumed, the output so far will be repeated,
|
||||||
|
either in full or (due to memory limitations) with
|
||||||
|
.Dq Li <..>
|
||||||
|
in its body, before the remaining part of the system call is printed.
|
||||||
|
This time, the line will have a
|
||||||
|
.Sq Li *
|
||||||
|
asterisk in its prefix, to indicate that this is not a new system call:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
25812| write(1, "test\en", 5) = <..>
|
||||||
|
25813| setuid(0) = 0
|
||||||
|
25812|*write(1, "test\en", 5) = 5
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
Finally,
|
||||||
|
.Nm
|
||||||
|
prints three dashes on their own line whenever the process context (program
|
||||||
|
counter and/or stack pointer) is changed during a system call.
|
||||||
|
This feature intends to help identify blocks of code run from signal handlers.
|
||||||
|
The following example shows a SIGALRM signal handler being invoked.
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
sigsuspend([]) = ** SIGALRM ** -1 [EINTR]
|
||||||
|
---
|
||||||
|
sigprocmask(SIG_SETMASK, ~[], [ALRM]) = 0
|
||||||
|
sigreturn({sc_mask=[], ..})
|
||||||
|
---
|
||||||
|
exit(0)
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
However, the three dashes are not printed when a signal handler is invoked
|
||||||
|
while the program is not in a system call, because the tracer does not see such
|
||||||
|
invocations.
|
||||||
|
It is however also printed for successful
|
||||||
|
.Fn execve
|
||||||
|
calls.
|
||||||
|
.Sh DIAGNOSTICS
|
||||||
|
.Ex
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr ptrace 2
|
||||||
|
.Sh AUTHORS
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
utility was written by
|
||||||
|
.An David van Moolenbroek
|
||||||
|
.Aq david@minix3.org .
|
||||||
|
.Sh BUGS
|
||||||
|
While the utility aims to provide output for all system calls that can possibly
|
||||||
|
be made by user programs, output printers for a small number of rarely-used
|
||||||
|
structures and IOCTLs are still missing. In such cases, plain pointers will be
|
||||||
|
printed instead of actual contents.
|
||||||
|
.Pp
|
||||||
|
A signal arrives at the tracing process when sent to the target process, even
|
||||||
|
when the target process is blocking the signal and will thus receive it later.
|
||||||
|
This is a limitation of the ptrace infrastructure, although it does ensure that
|
||||||
|
a target process is not able to block signals generated for tracing purposes.
|
||||||
|
The result is that signals are not always shown at the time that they are
|
||||||
|
taken in by the target process, and that stack traces for signals may be off.
|
||||||
|
.Pp
|
||||||
|
Attaching to system services is currently not supported, due to limitations of
|
||||||
|
the ptrace infrastructure. The
|
||||||
|
.Nm
|
||||||
|
utility will detect and safely detach from system services, though.
|
817
minix/usr.bin/trace/trace.c
Normal file
817
minix/usr.bin/trace/trace.c
Normal file
|
@ -0,0 +1,817 @@
|
||||||
|
/* trace(1) - the MINIX3 system call tracer - by D.C. van Moolenbroek */
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
/* Global variables, used only for a subset of the command line options. */
|
||||||
|
int allnames; /* FALSE = structure field names, TRUE = all names */
|
||||||
|
unsigned int valuesonly; /* 0 = normal, 1 = no symbols, 2 = no structures */
|
||||||
|
unsigned int verbose; /* 0 = essentials, 1 = elaborate, 2 = everything */
|
||||||
|
|
||||||
|
/* Local variables, for signal handling. */
|
||||||
|
static int got_signal, got_info;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Signal handler for signals that are supposed to make us terminate. Let the
|
||||||
|
* main loop do the actual work, since it might be in the middle of processing
|
||||||
|
* a process status change right now.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
sig_handler(int __unused sig)
|
||||||
|
{
|
||||||
|
|
||||||
|
got_signal = TRUE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Signal handler for the SIGINFO signal. Let the main loop report on all
|
||||||
|
* processes currenty being traced. Since SIGINFO is sent to the current
|
||||||
|
* process group, traced children may get the signal as well. This is both
|
||||||
|
* intentional and impossible to prevent.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
info_handler(int __unused sig)
|
||||||
|
{
|
||||||
|
|
||||||
|
got_info = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a list of traced processes and their call status. We must not
|
||||||
|
* interfere with actual process output, so perform out-of-band printing
|
||||||
|
* (with info lines rather than lines prefixed by each process's PID).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
list_info(void)
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
int no_call, in_call;
|
||||||
|
|
||||||
|
put_newline();
|
||||||
|
|
||||||
|
for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
|
||||||
|
/*
|
||||||
|
* When attaching to an existing process, there is no way to
|
||||||
|
* find out whether the process is in a system call or not.
|
||||||
|
*/
|
||||||
|
no_call = (proc->trace_flags & TF_NOCALL);
|
||||||
|
in_call = (proc->trace_flags & TF_INCALL);
|
||||||
|
assert(!in_call || !no_call);
|
||||||
|
|
||||||
|
put_fmt(NULL, "Tracing %s (pid %d), %s%s%s", proc->name,
|
||||||
|
proc->pid, no_call ? "call status unknown" :
|
||||||
|
(in_call ? "in a " : "not in a call"),
|
||||||
|
in_call ? call_name(proc) : "",
|
||||||
|
in_call ? " call" : "");
|
||||||
|
put_newline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Either we have just started or attached to the given process, it the process
|
||||||
|
* has performed a successful execve() call. Obtain the new process name, and
|
||||||
|
* print a banner for it.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
new_exec(struct trace_proc * proc)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Failure to obtain the process name is worrisome, but not fatal.. */
|
||||||
|
if (kernel_get_name(proc->pid, proc->name, sizeof(proc->name)) < 0)
|
||||||
|
strlcpy(proc->name, "<unknown>", sizeof(proc->name));
|
||||||
|
|
||||||
|
put_newline();
|
||||||
|
put_fmt(proc, "Tracing %s (pid %d)", proc->name, proc->pid);
|
||||||
|
put_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have started or attached to a process. Set the appropriate flags, and
|
||||||
|
* print a banner showing that we are now tracing it.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
new_proc(struct trace_proc * proc, int follow_fork)
|
||||||
|
{
|
||||||
|
int fl;
|
||||||
|
|
||||||
|
/* Set the desired tracing options. */
|
||||||
|
fl = TO_ALTEXEC;
|
||||||
|
if (follow_fork) fl |= TO_TRACEFORK;
|
||||||
|
|
||||||
|
(void)ptrace(T_SETOPT, proc->pid, 0, fl);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When attaching to an arbitrary process, this process might be in the
|
||||||
|
* middle of an execve(). Now that we have enabled TO_ALTEXEC, we may
|
||||||
|
* now get a SIGSTOP signal next. Guard against this by marking the
|
||||||
|
* first system call as a possible execve().
|
||||||
|
*/
|
||||||
|
if ((proc->trace_flags & (TF_ATTACH | TF_STOPPING)) == TF_ATTACH)
|
||||||
|
proc->trace_flags |= TF_EXEC;
|
||||||
|
|
||||||
|
new_exec(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A process has terminated or is being detached. Print the resulting status.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
discard_proc(struct trace_proc * proc, int status)
|
||||||
|
{
|
||||||
|
const char *signame;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The exit() calls are of type no-return, meaning they are expected
|
||||||
|
* not to return. However, calls of this type may in fact return an
|
||||||
|
* error, in which case the error must be printed. Thus, such calls
|
||||||
|
* are not actually finished until the end of the call-leave phase.
|
||||||
|
* For exit() calls, a successful call will never get to the call-leave
|
||||||
|
* phase. The result is that such calls will end up being shown as
|
||||||
|
* suspended, which is unintuitive. To counter this, we pretend that a
|
||||||
|
* clean process exit is in fact preceded by a call-leave event, thus
|
||||||
|
* allowing the call to be printed without suspension. An example:
|
||||||
|
*
|
||||||
|
* 3| exit(0) <..>
|
||||||
|
* 2| setsid() = 2
|
||||||
|
* [A] 3| exit(0)
|
||||||
|
* 3| Process exited normally with code 0
|
||||||
|
*
|
||||||
|
* The [A] line is the result of the following code.
|
||||||
|
*/
|
||||||
|
if (WIFEXITED(status) && (proc->trace_flags & TF_INCALL))
|
||||||
|
call_leave(proc, TRUE /*skip*/);
|
||||||
|
|
||||||
|
put_newline();
|
||||||
|
if (WIFEXITED(status)) {
|
||||||
|
put_fmt(proc, "Process exited normally with code %d",
|
||||||
|
WEXITSTATUS(status));
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
if ((signame = get_signal_name(WTERMSIG(status))) != NULL)
|
||||||
|
put_fmt(proc, "Process terminated from signal %s",
|
||||||
|
signame);
|
||||||
|
else
|
||||||
|
put_fmt(proc, "Process terminated from signal %d",
|
||||||
|
WTERMSIG(status));
|
||||||
|
} else if (WIFSTOPPED(status))
|
||||||
|
put_text(proc, "Process detached");
|
||||||
|
else
|
||||||
|
put_fmt(proc, "Bogus wait result (%04x)", status);
|
||||||
|
put_newline();
|
||||||
|
|
||||||
|
proc_del(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The given process has been stopped on a system call, either entering or
|
||||||
|
* leaving that call.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
handle_call(struct trace_proc * proc, int show_stack)
|
||||||
|
{
|
||||||
|
reg_t pc, sp;
|
||||||
|
int class, skip, new_ctx;
|
||||||
|
|
||||||
|
proc->trace_flags &= ~TF_NOCALL;
|
||||||
|
|
||||||
|
if (proc->trace_flags & TF_SKIP) {
|
||||||
|
/* Skip the call leave phase after a successful execve(). */
|
||||||
|
proc->trace_flags &= ~(TF_INCALL | TF_SKIP);
|
||||||
|
} else if (!(proc->trace_flags & TF_INCALL)) {
|
||||||
|
/*
|
||||||
|
* The call_enter call returns the class of the call:
|
||||||
|
* TC_NORMAL, TC_EXEC, or TC_SIGRET. TC_EXEC means that an
|
||||||
|
* execve() call is being performed. This means that if a
|
||||||
|
* SIGSTOP follows for the current process, the process has
|
||||||
|
* successfully started a different executable. TC_SIGRET
|
||||||
|
* means that if successful, the call will have a bogus return
|
||||||
|
* value. TC_NORMAL means that the call requires no exception.
|
||||||
|
*/
|
||||||
|
class = call_enter(proc, show_stack);
|
||||||
|
|
||||||
|
switch (class) {
|
||||||
|
case TC_NORMAL:
|
||||||
|
break;
|
||||||
|
case TC_EXEC:
|
||||||
|
proc->trace_flags |= TF_EXEC;
|
||||||
|
break;
|
||||||
|
case TC_SIGRET:
|
||||||
|
proc->trace_flags |= TF_CTX_SKIP;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save the current program counter and stack pointer. */
|
||||||
|
if (!kernel_get_context(proc->pid, &pc, &sp, NULL /*fp*/)) {
|
||||||
|
proc->last_pc = pc;
|
||||||
|
proc->last_sp = sp;
|
||||||
|
} else
|
||||||
|
proc->last_pc = proc->last_sp = 0;
|
||||||
|
|
||||||
|
proc->trace_flags |= TF_INCALL;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Check if the program counter or stack pointer have changed
|
||||||
|
* during the system call. If so, this is a strong indication
|
||||||
|
* that a sigreturn call has succeeded, and thus its result
|
||||||
|
* must be skipped, since the result register will not contain
|
||||||
|
* the result of the call.
|
||||||
|
*/
|
||||||
|
new_ctx = (proc->last_pc != 0 &&
|
||||||
|
!kernel_get_context(proc->pid, &pc, &sp, NULL /*fp*/) &&
|
||||||
|
(pc != proc->last_pc || sp != proc->last_sp));
|
||||||
|
|
||||||
|
skip = ((proc->trace_flags & TF_CTX_SKIP) && new_ctx);
|
||||||
|
|
||||||
|
call_leave(proc, skip);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On such context changes, also print a short dashed line.
|
||||||
|
* This helps in identifying signal handler invocations,
|
||||||
|
* although it is not reliable for that purpose: no dashed line
|
||||||
|
* will be printed if a signal handler is invoked while the
|
||||||
|
* process is not making a system call.
|
||||||
|
*/
|
||||||
|
if (new_ctx) {
|
||||||
|
put_text(proc, "---");
|
||||||
|
put_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
proc->trace_flags &= ~(TF_INCALL | TF_CTX_SKIP | TF_EXEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The given process has received the given signal. Report the receipt. Due
|
||||||
|
* to the way that signal handling with traced processes works, the signal may
|
||||||
|
* in fact be delivered to the process much later, or never--a problem inherent
|
||||||
|
* to the way signals are handled in PM right now (namely, deferring signal
|
||||||
|
* delivery would let the traced process block signals meant for the tracer).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
report_signal(struct trace_proc * proc, int sig, int show_stack)
|
||||||
|
{
|
||||||
|
const char *signame;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a stack trace only if we are not in a call; otherwise, we
|
||||||
|
* would simply get the same stack trace twice and mess up the output
|
||||||
|
* in the process, because call suspension is not expected if we are
|
||||||
|
* tracing a single process only.
|
||||||
|
* FIXME: the check should be for whether we actually print the call..
|
||||||
|
*/
|
||||||
|
if (show_stack && !(proc->trace_flags & TF_INCALL))
|
||||||
|
kernel_put_stacktrace(proc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this process is in the middle of a call, the signal will be
|
||||||
|
* printed within the call. This will always happen on the call split,
|
||||||
|
* that is, between the call's entering (out) and leaving (in) phases.
|
||||||
|
* This also means that the recording of the call-enter phase may be
|
||||||
|
* replayed more than once, and the call may be suspended more than
|
||||||
|
* once--after all, a signal is not necessarily followed immediately
|
||||||
|
* by the call result. If the process is not in the middle of a call,
|
||||||
|
* the signal will end up on a separate line. In both cases, multiple
|
||||||
|
* consecutive signals may be printed right after one another. The
|
||||||
|
* following scenario shows a number of possible combinations:
|
||||||
|
*
|
||||||
|
* 2| foo(<..>
|
||||||
|
* 3| ** SIGHUP ** ** SIGUSR1 **
|
||||||
|
* 3| bar() = <..>
|
||||||
|
* 2|*foo(** SIGUSR1 ** ** SIGUSR2 ** <..>
|
||||||
|
* 3|*bar() = ** SIGCHLD ** 0
|
||||||
|
* 2|*foo(** SIGINT ** &0xef852000) = -1 [EINTR]
|
||||||
|
* 3| kill(3, SIGTERM) = ** SIGTERM ** <..>
|
||||||
|
* 3| Process terminated from signal SIGTERM
|
||||||
|
*/
|
||||||
|
|
||||||
|
call_replay(proc);
|
||||||
|
|
||||||
|
if (!valuesonly && (signame = get_signal_name(sig)) != NULL)
|
||||||
|
put_fmt(proc, "** %s **", signame);
|
||||||
|
else
|
||||||
|
put_fmt(proc, "** SIGNAL %d **", sig);
|
||||||
|
|
||||||
|
put_space(proc);
|
||||||
|
|
||||||
|
output_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for the given process ID to stop on the given signal. Upon success,
|
||||||
|
* the function will return zero. Upon failure, it will return -1, and errno
|
||||||
|
* will be either set to an error code, or to zero in order to indicate that
|
||||||
|
* the process exited instead.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
wait_sig(pid_t pid, int sig)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (waitpid(pid, &status, 0) == -1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WIFSTOPPED(status)) {
|
||||||
|
/* The process terminated just now. */
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WSTOPSIG(status) == sig)
|
||||||
|
break;
|
||||||
|
|
||||||
|
(void)ptrace(T_RESUME, pid, 0, WSTOPSIG(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attach to the given process, and wait for the resulting SIGSTOP signal.
|
||||||
|
* Other signals may arrive first; we pass these on to the process without
|
||||||
|
* reporting them, thus logically modelling them as having arrived before we
|
||||||
|
* attached to the process. The process might also exit in the meantime,
|
||||||
|
* typically as a result of a lethal signal; following the same logical model,
|
||||||
|
* we pretend the process did not exist in the first place. Since the SIGSTOP
|
||||||
|
* signal will be pending right after attaching to the process, this procedure
|
||||||
|
* will never block.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
attach(pid_t pid)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (ptrace(T_ATTACH, pid, 0, 0) != 0) {
|
||||||
|
warn("Unable to attach to pid %d", pid);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wait_sig(pid, SIGSTOP) != 0) {
|
||||||
|
/* If the process terminated, report it as not found. */
|
||||||
|
if (errno == 0)
|
||||||
|
errno = ESRCH;
|
||||||
|
|
||||||
|
warn("Unable to attach to pid %d", pid);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify that we can read values from the kernel at all. */
|
||||||
|
if (kernel_check(pid) == FALSE) {
|
||||||
|
(void)ptrace(T_DETACH, pid, 0, 0);
|
||||||
|
|
||||||
|
warnx("Kernel magic check failed, recompile trace(1)");
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* System services are managed by RS, which prevents them from
|
||||||
|
* being traced properly by PM. Attaching to a service could
|
||||||
|
* therefore cause problems, so we should detach immediately.
|
||||||
|
*/
|
||||||
|
if (kernel_is_service(pid) == TRUE) {
|
||||||
|
(void)ptrace(T_DETACH, pid, 0, 0);
|
||||||
|
|
||||||
|
warnx("Cannot attach to system services!");
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Detach from all processes, knowning that they were all processes to which we
|
||||||
|
* attached explicitly (i.e., not started by us) and are all currently stopped.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
detach_stopped(void)
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
|
||||||
|
for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc))
|
||||||
|
(void)ptrace(T_DETACH, proc->pid, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start detaching from all processes to which we previously attached. The
|
||||||
|
* function is expected to return before detaching is completed, and the caller
|
||||||
|
* must deal with the new situation appropriately. Do not touch any processes
|
||||||
|
* started by us (to allow graceful termination), unless force is set, in which
|
||||||
|
* case those processes are killed.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
detach_running(int force)
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
|
||||||
|
for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
|
||||||
|
if (proc->trace_flags & TF_ATTACH) {
|
||||||
|
/* Already detaching? Then do nothing. */
|
||||||
|
if (proc->trace_flags & TF_DETACH)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(proc->trace_flags & TF_STOPPING))
|
||||||
|
(void)kill(proc->pid, SIGSTOP);
|
||||||
|
|
||||||
|
proc->trace_flags |= TF_DETACH | TF_STOPPING;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* The child processes may be ignoring SIGINTs, so upon
|
||||||
|
* the second try, force them to terminate.
|
||||||
|
*/
|
||||||
|
if (force)
|
||||||
|
(void)kill(proc->pid, SIGKILL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print command usage.
|
||||||
|
*/
|
||||||
|
static void __dead
|
||||||
|
usage(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
(void)fprintf(stderr, "usage: %s [-fgNsVv] [-o file] [-p pid] "
|
||||||
|
"[command]\n", getprogname());
|
||||||
|
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The main function of the system call tracer.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
main(int argc, char * argv[])
|
||||||
|
{
|
||||||
|
struct trace_proc *proc;
|
||||||
|
const char *output_file;
|
||||||
|
int status, sig, follow_fork, show_stack, grouping, first_signal;
|
||||||
|
pid_t pid, last_pid;
|
||||||
|
int c, error;
|
||||||
|
|
||||||
|
setprogname(argv[0]);
|
||||||
|
|
||||||
|
proc_init();
|
||||||
|
|
||||||
|
follow_fork = FALSE;
|
||||||
|
show_stack = FALSE;
|
||||||
|
grouping = FALSE;
|
||||||
|
output_file = NULL;
|
||||||
|
|
||||||
|
allnames = FALSE;
|
||||||
|
verbose = 0;
|
||||||
|
valuesonly = 0;
|
||||||
|
|
||||||
|
while ((c = getopt(argc, argv, "fgNsVvo:p:")) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'f':
|
||||||
|
follow_fork = TRUE;
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
grouping = TRUE;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
allnames = TRUE;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
show_stack = TRUE;
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
valuesonly++;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
verbose++;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
output_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
pid = atoi(optarg);
|
||||||
|
if (pid <= 0)
|
||||||
|
usage();
|
||||||
|
|
||||||
|
if (proc_get(pid) == NULL && proc_add(pid) == NULL)
|
||||||
|
err(EXIT_FAILURE, NULL);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argv += optind;
|
||||||
|
argc -= optind;
|
||||||
|
|
||||||
|
first_signal = TRUE;
|
||||||
|
got_signal = FALSE;
|
||||||
|
got_info = FALSE;
|
||||||
|
|
||||||
|
signal(SIGINT, sig_handler);
|
||||||
|
signal(SIGINFO, info_handler);
|
||||||
|
|
||||||
|
/* Attach to any processes for which PIDs were given. */
|
||||||
|
for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
|
||||||
|
if (attach(proc->pid) != 0) {
|
||||||
|
/*
|
||||||
|
* Detach from the processes that we have attached to
|
||||||
|
* so far, i.e. the ones with the TF_ATTACH flag.
|
||||||
|
*/
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
proc->trace_flags = TF_ATTACH | TF_NOCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If a command is given, start a child that executes the command. */
|
||||||
|
if (argc >= 1) {
|
||||||
|
pid = fork();
|
||||||
|
|
||||||
|
switch (pid) {
|
||||||
|
case -1:
|
||||||
|
warn("Unable to fork");
|
||||||
|
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
(void)ptrace(T_OK, 0, 0, 0);
|
||||||
|
|
||||||
|
(void)execvp(argv[0], argv);
|
||||||
|
|
||||||
|
err(EXIT_FAILURE, "Unable to start %s", argv[0]);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The first signal will now be SIGTRAP from the execvp(),
|
||||||
|
* unless that fails, in which case the child will terminate.
|
||||||
|
*/
|
||||||
|
if (wait_sig(pid, SIGTRAP) != 0) {
|
||||||
|
/*
|
||||||
|
* If the child exited, the most likely cause is a
|
||||||
|
* failure to execute the command. Let the child
|
||||||
|
* report the error, and do not say anything here.
|
||||||
|
*/
|
||||||
|
if (errno != 0)
|
||||||
|
warn("Unable to start process");
|
||||||
|
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we haven't already, perform the kernel magic check. */
|
||||||
|
if (proc_count() == 0 && kernel_check(pid) == FALSE) {
|
||||||
|
warnx("Kernel magic check failed, recompile trace(1)");
|
||||||
|
|
||||||
|
(void)kill(pid, SIGKILL);
|
||||||
|
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((proc = proc_add(pid)) == NULL) {
|
||||||
|
warn(NULL);
|
||||||
|
|
||||||
|
(void)kill(pid, SIGKILL);
|
||||||
|
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
proc->trace_flags = 0;
|
||||||
|
} else
|
||||||
|
pid = -1;
|
||||||
|
|
||||||
|
/* The user will have to give us at least one process to trace. */
|
||||||
|
if (proc_count() == 0)
|
||||||
|
usage();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open an alternative output file if needed. After that, standard
|
||||||
|
* error should no longer be used directly, and all output has to go
|
||||||
|
* through the output module.
|
||||||
|
*/
|
||||||
|
if (output_init(output_file) < 0) {
|
||||||
|
warn("Unable to open output file");
|
||||||
|
|
||||||
|
if (pid > 0)
|
||||||
|
(void)kill(pid, SIGKILL);
|
||||||
|
|
||||||
|
detach_stopped();
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All the traced processes are currently stopped. Initialize, report,
|
||||||
|
* and resume them.
|
||||||
|
*/
|
||||||
|
for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
|
||||||
|
new_proc(proc, follow_fork);
|
||||||
|
|
||||||
|
(void)ptrace(T_SYSCALL, proc->pid, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle events until there are no traced processes left.
|
||||||
|
*/
|
||||||
|
last_pid = 0;
|
||||||
|
error = FALSE;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* If an output error occurred, exit as soon as possible. */
|
||||||
|
if (!error && output_error()) {
|
||||||
|
detach_running(TRUE /*force*/);
|
||||||
|
|
||||||
|
error = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the user pressed ^C once, start detaching the processes
|
||||||
|
* that we did not start, if any. If the user pressed ^C
|
||||||
|
* twice, kill the process that we did start, if any.
|
||||||
|
*/
|
||||||
|
if (got_signal) {
|
||||||
|
detach_running(!first_signal);
|
||||||
|
|
||||||
|
got_signal = FALSE;
|
||||||
|
first_signal = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upon getting SIGINFO, print a list of traced processes. */
|
||||||
|
if (got_info) {
|
||||||
|
list_info();
|
||||||
|
|
||||||
|
got_info = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Block until something happens to a traced process. If
|
||||||
|
* enabled from the command line, first try waiting for the
|
||||||
|
* last process for which we got results, so as to reduce call
|
||||||
|
* suspensions a bit.
|
||||||
|
*/
|
||||||
|
if (grouping && last_pid > 0 &&
|
||||||
|
waitpid(last_pid, &status, WNOHANG) > 0)
|
||||||
|
pid = last_pid;
|
||||||
|
else
|
||||||
|
if ((pid = waitpid(-1, &status, 0)) <= 0) {
|
||||||
|
if (pid == -1 && errno == EINTR) continue;
|
||||||
|
if (pid == -1 && errno == ECHILD) break; /* all done */
|
||||||
|
|
||||||
|
put_fmt(NULL, "Unexpected waitpid failure: %s",
|
||||||
|
(pid == 0) ? "No result" : strerror(errno));
|
||||||
|
put_newline();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need waitpid to function correctly in order to
|
||||||
|
* detach from any attached processes, so we can do
|
||||||
|
* little more than just exit, effectively killing all
|
||||||
|
* traced processes.
|
||||||
|
*/
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pid = 0;
|
||||||
|
|
||||||
|
/* Get the trace data structure for the process. */
|
||||||
|
if ((proc = proc_get(pid)) == NULL) {
|
||||||
|
/*
|
||||||
|
* The waitpid() call returned the status of a process
|
||||||
|
* that we have not yet seen. This must be a newly
|
||||||
|
* forked child. If it is not stopped, it must have
|
||||||
|
* died immediately, and we choose not to report it.
|
||||||
|
*/
|
||||||
|
if (!WIFSTOPPED(status))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((proc = proc_add(pid)) == NULL) {
|
||||||
|
put_fmt(NULL,
|
||||||
|
"Error attaching to new child %d: %s",
|
||||||
|
pid, strerror(errno));
|
||||||
|
put_newline();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Out of memory allocating a new child object!
|
||||||
|
* We can not trace this child, so just let it
|
||||||
|
* run free by detaching from it.
|
||||||
|
*/
|
||||||
|
if (WSTOPSIG(status) != SIGSTOP) {
|
||||||
|
(void)ptrace(T_RESUME, pid, 0,
|
||||||
|
WSTOPSIG(status));
|
||||||
|
|
||||||
|
if (wait_sig(pid, SIGSTOP) != 0)
|
||||||
|
continue; /* it died.. */
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)ptrace(T_DETACH, pid, 0, 0);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must specify TF_ATTACH here, even though it may
|
||||||
|
* be a child of a process we started, in which case it
|
||||||
|
* should be killed when we exit. We do not keep track
|
||||||
|
* of ancestry though, so better safe than sorry.
|
||||||
|
*/
|
||||||
|
proc->trace_flags = TF_ATTACH | TF_STOPPING;
|
||||||
|
|
||||||
|
new_proc(proc, follow_fork);
|
||||||
|
|
||||||
|
/* Repeat entering the fork call for the child. */
|
||||||
|
handle_call(proc, show_stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the process died, report its status and clean it up. */
|
||||||
|
if (!WIFSTOPPED(status)) {
|
||||||
|
discard_proc(proc, status);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = WSTOPSIG(status);
|
||||||
|
|
||||||
|
if (sig == SIGSTOP && (proc->trace_flags & TF_STOPPING)) {
|
||||||
|
/* We expected the process to be stopped; now it is. */
|
||||||
|
proc->trace_flags &= ~TF_STOPPING;
|
||||||
|
|
||||||
|
if (proc->trace_flags & TF_DETACH) {
|
||||||
|
if (ptrace(T_DETACH, proc->pid, 0, 0) == 0)
|
||||||
|
discard_proc(proc, status);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If detaching failed, the process must have
|
||||||
|
* died, and we'll get notified through wait().
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = 0;
|
||||||
|
} else if (sig == SIGSTOP && (proc->trace_flags & TF_EXEC)) {
|
||||||
|
/* The process has performed a successful execve(). */
|
||||||
|
call_leave(proc, TRUE /*skip*/);
|
||||||
|
|
||||||
|
put_text(proc, "---");
|
||||||
|
|
||||||
|
new_exec(proc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A successful execve() has no result, in the sense
|
||||||
|
* that there is no reply message. We should therefore
|
||||||
|
* not even try to copy in the reply message from the
|
||||||
|
* original location, because it will be invalid.
|
||||||
|
* Thus, we skip the exec's call leave phase entirely.
|
||||||
|
*/
|
||||||
|
proc->trace_flags &= ~TF_EXEC;
|
||||||
|
proc->trace_flags |= TF_SKIP;
|
||||||
|
|
||||||
|
sig = 0;
|
||||||
|
} else if (sig == SIGTRAP) {
|
||||||
|
/* The process is entering or leaving a system call. */
|
||||||
|
if (!(proc->trace_flags & TF_DETACH))
|
||||||
|
handle_call(proc, show_stack);
|
||||||
|
|
||||||
|
sig = 0;
|
||||||
|
} else {
|
||||||
|
/* The process has received a signal. */
|
||||||
|
report_signal(proc, sig, show_stack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only in this case do we pass the signal to the
|
||||||
|
* traced process.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resume process execution. If this call fails, the process
|
||||||
|
* has probably died. We will find out soon enough.
|
||||||
|
*/
|
||||||
|
(void)ptrace(T_SYSCALL, proc->pid, 0, sig);
|
||||||
|
|
||||||
|
last_pid = proc->pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (error) ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||||
|
}
|
32
minix/usr.bin/trace/type.h
Normal file
32
minix/usr.bin/trace/type.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
#define COUNT(s) (sizeof(s) / sizeof(s[0]))
|
||||||
|
|
||||||
|
struct call_handler {
|
||||||
|
const char *name;
|
||||||
|
const char *(*namefunc)(const message *m_out);
|
||||||
|
int (*outfunc)(struct trace_proc *proc, const message *m_out);
|
||||||
|
void (*infunc)(struct trace_proc *proc, const message *m_out,
|
||||||
|
const message *m_in, int failed);
|
||||||
|
};
|
||||||
|
#define HANDLER(n,o,i) { .name = n, .outfunc = o, .infunc = i }
|
||||||
|
#define HANDLER_NAME(n,o,i) { .namefunc = n, .outfunc = o, .infunc = i }
|
||||||
|
|
||||||
|
struct calls {
|
||||||
|
endpoint_t endpt;
|
||||||
|
unsigned int base;
|
||||||
|
const struct call_handler *map;
|
||||||
|
unsigned int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct flags {
|
||||||
|
unsigned int mask;
|
||||||
|
unsigned int value;
|
||||||
|
const char *name;
|
||||||
|
};
|
||||||
|
#define FLAG(f) { f, f, #f }
|
||||||
|
#define FLAG_MASK(m,f) { m, f, #f }
|
||||||
|
#define FLAG_ZERO(f) { ~0, f, #f }
|
||||||
|
|
||||||
|
/* not great, but it prevents a massive potential for typos.. */
|
||||||
|
#define NAME(r) case r: return #r
|
||||||
|
#define TEXT(v) case v: text = #v; break
|
Loading…
Reference in a new issue