trace(1): document how to add an IOCTL handler
Also fix two small IOCTL-related bugs: - do not print an argument pointer for argument-less IOCTLs; - print IOCTL contents with -V given once, just like structures. Change-Id: Iec7373003d71937fd34ee4b9db6c6cec0c916411
This commit is contained in:
parent
92601f58cb
commit
8b18d03deb
2 changed files with 116 additions and 4 deletions
|
@ -201,6 +201,112 @@ functions for call with no parameters, and for functions which need no more
|
||||||
than put_result() to print their system call result, respectively.
|
than put_result() to print their system call result, respectively.
|
||||||
|
|
||||||
|
|
||||||
|
ADDING AN IOCTL HANDLER
|
||||||
|
|
||||||
|
There are many IOCTL requests, and many have their own associated data types.
|
||||||
|
Like with system calls, the idea is to provide an actual implementation for any
|
||||||
|
IOCTLs that can actually occur in the wild. This consists of printing the full
|
||||||
|
IOCTL name, as well as its argument. First something about how handling IOCTLs
|
||||||
|
is grouped into files in the ioctl subdirectory, then about the actual
|
||||||
|
procedure the IOCTLs are handled.
|
||||||
|
|
||||||
|
Grouping of IOCTL handling in the ioctl subdirectory is currently based on the
|
||||||
|
IOCTLs' associated device type. This is not a performance optimization: for
|
||||||
|
any given IOCTL, there is no way for the main IOCTL code (in ioctl.c) to know
|
||||||
|
which group, if any, contains a handler for the IOCTL, so it simply queries all
|
||||||
|
groups. The grouping is there only to keep down the size of individual source
|
||||||
|
files, and as such not even strict: for example, networking IOCTLs are
|
||||||
|
technically a subset of character IOCTLs, and kept separate only because there
|
||||||
|
are so many of them. The point here is mainly that the separation is not at
|
||||||
|
all set in stone. However, the svrctl group is an exception: svrctl(2)
|
||||||
|
requests are very much like IOCTLs, and thus also treated as such, but they are
|
||||||
|
in a different namespace. Thus, their handlers are in a separate file.
|
||||||
|
|
||||||
|
As per the ioctl_table structure, each group has a function to return the name
|
||||||
|
of an IOCTL it knows (typically <group>_ioctl_name), and a function to handle
|
||||||
|
IOCTL arguments (typically <group>_ioctl_arg). Whenever an IOCTL system call
|
||||||
|
is made, each group's name function is queried. This function has the
|
||||||
|
following prototype:
|
||||||
|
|
||||||
|
const char *group_ioctl_name(unsigned long req);
|
||||||
|
|
||||||
|
The "req" parameter contains the IOCTL request code. The function is to return
|
||||||
|
a static non-NULL string if it knows the name for the request code, or NULL
|
||||||
|
otherwise. If the function returns a non-NULL string, that name will be used
|
||||||
|
for the IOCTL. In addition, if the IOCTL has an argument at all, i.e. it is
|
||||||
|
not of the basic _IO() type, that group (and only that group!) will be queried
|
||||||
|
about the IOCTL argument, by calling the group's IOCTL argument function. The
|
||||||
|
IOCTL argument function has the following prototype:
|
||||||
|
|
||||||
|
int group_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
|
||||||
|
int dir);
|
||||||
|
|
||||||
|
For a single IOCTL, this function may be called up to three times. The first
|
||||||
|
time, "ptr" will be NULL, and based on the same IOCTL request code "req", the
|
||||||
|
function must return any bitwise combination of two flags: IF_OUT and IF_IN.
|
||||||
|
|
||||||
|
The returned flags determine whether and how the IOCTL's argument will be
|
||||||
|
printed: before and/or after performing the IOCTL system call. These two flags
|
||||||
|
effectively correspond to the "write" and "read" argument directions of IOCTLs:
|
||||||
|
IF_OUT indicates that the argument should be printed before the IOCTL request,
|
||||||
|
and this is to be used only for IOCTLs of type _IOW() and _IOWR(). IF_IN
|
||||||
|
indicates that the argument should be printed after the IOCTL request (but if
|
||||||
|
it was successful only), and is to be used only for IOCTLs of type _IOR() and
|
||||||
|
_IOWR().
|
||||||
|
|
||||||
|
The returned flag combination determines how the IOCTL is formatted. The
|
||||||
|
following possible return values result in the following output formats, again
|
||||||
|
with the "|" indicating the call split, "out" being the IOCTL argument contents
|
||||||
|
printed before the IOCTL call, and "in" being the IOCTL argument printed after
|
||||||
|
the IOCTL call:
|
||||||
|
|
||||||
|
0: ioctl(3, IOCFOO, &0xaddress) = |0
|
||||||
|
IF_OUT: ioctl(3, IOCFOO, {out}) = |0
|
||||||
|
IF_OUT|IF_IN: ioctl(3, IOCFOO, {out}) = |0 {in}
|
||||||
|
IF_IN: ioctl(3, IOCFOO, |{in}) = 0
|
||||||
|
|
||||||
|
Both IF_ flags are optional, mainly because it is not always needed to print
|
||||||
|
both sides for an _IOWR() request. However, using the wrong flag (e.g., IF_OUT
|
||||||
|
for an _IOR() request, which simply makes no sense) will trigger an assert.
|
||||||
|
Also, the function should basically never return 0 for an IOCTL it recognizes.
|
||||||
|
Again, for IOCTLs of type _IO(), which have no argument, the argument function
|
||||||
|
is not called at all.
|
||||||
|
|
||||||
|
Now the important part. For each flag that is returned on the initial call to
|
||||||
|
the argument function, the argument function will be called again, this time to
|
||||||
|
perform actual printing of the argument. For these subsequent calls, "ptr"
|
||||||
|
will point to the argument data which has been copied to the local address
|
||||||
|
space, and "dir" will contain one of the returned flags (that is, either IF_OUT
|
||||||
|
or IF_IN) to indicate whether the function is called before or after the IOCTL
|
||||||
|
call. As should now be obvious, if the first call returned IF_OUT | IF_IN, the
|
||||||
|
function will be called again with "dir" set to IF_OUT, and if the IOCTL call
|
||||||
|
did not fail, once more (for the third time), now with "dir" set to IF_IN.
|
||||||
|
|
||||||
|
For these calls with an actual "ptr" value and a direction, the function should
|
||||||
|
indeed print the argument as appropriate, using "proc" as process pointer for
|
||||||
|
use in calls to the printing functions. The general approach is to print non-
|
||||||
|
structure arguments as single values with no field name, and structure
|
||||||
|
arguments by printing its fields with their field names. The main code (in
|
||||||
|
ioctl.c) ensures that the output is enclosed in curly brackets, thus making the
|
||||||
|
output look like a structure anyway.
|
||||||
|
|
||||||
|
For these subsequent calls, the argument function's return value should be
|
||||||
|
IF_ALL if all parts of the IOCTL argument have been printed, or 0 otherwise.
|
||||||
|
In the latter case, the main code will add a final ".." field to indicate to
|
||||||
|
the user that not all parts of the argument have been printed, very much like
|
||||||
|
the "all" parameter of put_close_struct.
|
||||||
|
|
||||||
|
If no name can be found for the IOCTL request code, the argument will simply be
|
||||||
|
printed as a pointer. The same happens in error cases, for example if copying
|
||||||
|
in the IOCTL data resulted in an error.
|
||||||
|
|
||||||
|
There is no support for dealing with multiple IOCTLs with the exact same
|
||||||
|
request code--something that should not, but sadly does, occur in practice.
|
||||||
|
For now, the preferred approach would be to implement only support for the
|
||||||
|
IOCTL that is most likely to be found in practice, and possibly to put a horse
|
||||||
|
head in the bed of whoever introduced the duplicate request code.
|
||||||
|
|
||||||
|
|
||||||
INTERNALS: MULTIPROCESS OUTPUT AND PREEMPTION
|
INTERNALS: MULTIPROCESS OUTPUT AND PREEMPTION
|
||||||
|
|
||||||
Things get interesting when multiple processes are traced at once. Due to the
|
Things get interesting when multiple processes are traced at once. Due to the
|
||||||
|
|
|
@ -44,16 +44,19 @@ put_ioctl_req(struct trace_proc * proc, const char * name, unsigned long req,
|
||||||
* smart about looking up particular codes in each switch statement,
|
* smart about looking up particular codes in each switch statement,
|
||||||
* although in the worst case, it's a full O(n) lookup.
|
* although in the worst case, it's a full O(n) lookup.
|
||||||
*/
|
*/
|
||||||
for (i = 0; !valuesonly && i < COUNT(ioctl_table); i++) {
|
for (i = 0; i < COUNT(ioctl_table); i++) {
|
||||||
/* IOCTLs and SVRCTLs are considered different name spaces. */
|
/* IOCTLs and SVRCTLs are considered different name spaces. */
|
||||||
if (ioctl_table[i].is_svrctl != is_svrctl)
|
if (ioctl_table[i].is_svrctl != is_svrctl)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ((text = ioctl_table[i].name(req)) != NULL) {
|
if ((text = ioctl_table[i].name(req)) != NULL) {
|
||||||
put_field(proc, name, text);
|
|
||||||
|
|
||||||
proc->ioctl_index = i;
|
proc->ioctl_index = i;
|
||||||
|
|
||||||
|
if (valuesonly)
|
||||||
|
break;
|
||||||
|
|
||||||
|
put_field(proc, name, text);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,9 +109,12 @@ put_ioctl_arg_out(struct trace_proc * proc, const char * name,
|
||||||
dir = (_MINIX_IOCTL_IOW(req) ? IF_OUT : 0) |
|
dir = (_MINIX_IOCTL_IOW(req) ? IF_OUT : 0) |
|
||||||
(_MINIX_IOCTL_IOR(req) ? IF_IN : 0);
|
(_MINIX_IOCTL_IOR(req) ? IF_IN : 0);
|
||||||
|
|
||||||
if (dir == 0)
|
if (dir == 0) {
|
||||||
proc->ioctl_index = -1; /* no argument to print at all */
|
proc->ioctl_index = -1; /* no argument to print at all */
|
||||||
|
|
||||||
|
return CT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
/* No support for printing big-IOCTL contents just yet. */
|
/* No support for printing big-IOCTL contents just yet. */
|
||||||
if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
|
if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
|
||||||
proc->ioctl_index == -1) {
|
proc->ioctl_index == -1) {
|
||||||
|
|
Loading…
Reference in a new issue