Pointers to Structs
Referring to structs using pointers is very common in C and D. You
can use the operator -> to access struct members through a pointer.
If a struct s has a member m and you have a pointer
to this struct named sp (that is, sp is a variable of type struct s *),
you can either use the * operator to first dereference sp pointer in
order to access the member:
struct s *sp;
(*sp).m
or you can use the -> operator as a shorthand for this notation.
The following two D fragments are equivalent in meaning if sp is
a pointer to a struct:
(*sp).m sp->m
DTrace provides several built-in variables which are pointers to structs, including curpsinfo and
curlwpsinfo. These pointers refer to the structs psinfo and lwpsinfo respectively, and their content
provides a snapshot of information about the state of the current process and
lightweight process (LWP) associated with the thread that has fired the current probe.
A Solaris LWP is the kernel's representation of a user thread, upon which
the Solaris threads and POSIX threads interfaces are built. For convenience, DTrace exports
this information in the same form as the /proc filesystem files /proc/pid/psinfo and
/proc/pid/lwps/lwpid/lwpsinfo. The /proc structures are used by observability and debugging tools such as
ps(1), pgrep(1), and truss(1), and are defined in the system header file <sys/procfs.h> and
are described in the proc(4) man page. Here are few example expressions using curpsinfo, their
types, and their meanings:
curpsinfo->pr_pid |
pid_t |
current process ID |
curpsinfo->pr_fname |
char [] |
executable file name |
curpsinfo->pr_psargs |
char [] |
initial command line arguments |
You should review the complete structure definition later by examining the <sys/procfs.h> header
file and the corresponding descriptions in proc(4). The next example uses the pr_psargs
member to identify a process of interest by matching command-line arguments.
Structs are used frequently to create complex data structures in C programs, so
the ability to describe and reference structs from D also provides a
powerful capability for observing the inner workings of the Solaris operating system kernel and
its system interfaces. In addition to using the aforementioned curpsinfo struct, the
next example examines some kernel structs as well by observing the relationship between
the ksyms(7D) driver and read(2) requests. The driver makes use of two common
structs, known as uio(9S) and iovec(9S), to respond to requests to read from
the character device file /dev/ksyms.
The uio struct, accessed using the name struct uio or type alias uio_t,
is described in the uio(9S) man page and is used to describe an
I/O request that involves copying data between the kernel and a user
process. The uio in turn contains an array of one or more iovec(9S)
structures which each describe a piece of the requested I/O, in the event
that multiple chunks are requested using the readv(2) or writev(2) system calls. One
of the kernel device driver interface (DDI) routines that operates on struct uio is the
function uiomove(9F), which is one of a family of functions kernel drivers use
to respond to user process read(2) requests and copy data back to user processes.
The ksyms driver manages a character device file named /dev/ksyms, which appears to
be an ELF file containing information about the kernel's symbol table, but is
in fact an illusion created by the driver using the set of modules
that are currently loaded into the kernel. The driver uses the uiomove(9F) routine to
respond to read(2) requests. The next example illustrates that the arguments and calls
to read(2) from /dev/ksyms match the calls by the driver to uiomove(9F) to copy
the results back into the user address space at the location specified to
read(2).
We can use the strings(1) utility with the -a option to force a
bunch of reads from /dev/ksyms. Try running strings -a /dev/ksyms in your shell and see
what output it produces. In an editor, type in the first clause of
the example script and save it in a file named ksyms.d:
syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
printf("read %u bytes to user address %x\n", arg2, arg1);
}
This first clause uses the expression curpsinfo->pr_psargs to access and match the command-line
arguments of our strings(1) command so that the script selects the correct read(2)
requests before tracing the arguments. Notice that by using operator == with a left-hand
argument that is an array of char and a right-hand argument that is
a string, the D compiler infers that the left-hand argument should be promoted
to a string and a string comparison should be performed. Type in and
execute the command dtrace -q -s ksyms.d in one shell, and then type in the command
strings -a /dev/ksyms in another shell. As strings(1) executes, you will see output from DTrace similar
to the following example:
# dtrace -q -s ksyms.d
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
...
^C
#
This example can be extended using a common D programming technique to follow
a thread from this initial read(2) request deeper into the kernel. Upon
entry to the kernel in syscall::read:entry, the next script sets a thread-local flag variable
indicating this thread is of interest, and clears this flag on syscall::read:return. Once
the flag is set, it can be used as a predicate on other
probes to instrument kernel functions such as uiomove(9F). The DTrace function boundary tracing
(fbt) provider publishes probes for entry and return to functions defined within the
kernel, including those in the DDI. Type in the following source code which
uses the fbt provider to instrument uiomove(9F) and again save it in the file
ksyms.d:
Example 7-2 ksyms.d: Trace read(2) and uiomove(9F) Relationship
/*
* When our strings(1) invocation starts a read(2), set a watched flag on
* the current thread. When the read(2) finishes, clear the watched flag.
*/
syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
printf("read %u bytes to user address %x\n", arg2, arg1);
self->watched = 1;
}
syscall::read:return
/self->watched/
{
self->watched = 0;
}
/*
* Instrument uiomove(9F). The prototype for this function is as follows:
* int uiomove(caddr_t addr, size_t nbytes, enum uio_rw rwflag, uio_t *uio);
*/
fbt::uiomove:entry
/self->watched/
{
this->iov = args[3]->uio_iov;
printf("uiomove %u bytes to %p in pid %d\n",
this->iov->iov_len, this->iov->iov_base, pid);
}
The final clause of the example uses the thread-local variable self->watched to
identify when a kernel thread of interest enters the DDI routine uiomove(9F).
Once there, the script uses the built-in args array to access the fourth
argument (args[3]) to uiomove(), which is a pointer to the struct uio representing the
request. The D compiler automatically associates each member of the args array with the
type corresponding to the C function prototype for the instrumented kernel routine. The
uio_iov member contains a pointer to the struct iovec for the request. A copy
of this pointer is saved for use in our clause in the clause-local
variable this->iov. In the final statement, the script dereferences this->iov to access the iovec
members iov_len and iov_base, which represent the length in bytes and destination
base address for uiomove(9F), respectively. These values should match the input parameters to the
read(2) system call issued on the driver. Go to your shell and run
dtrace -q -s ksyms.d and then again enter the command strings -a /dev/ksyms in another shell. You should see
output similar to the following example:
# dtrace -q -s ksyms.d
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
...
^C
#
The addresses and process IDs will be different in your output, but you
should observe that the input arguments to read(2) match the parameters passed
to uiomove(9F) by the ksyms driver.