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. An Oracle Solaris LWP is the kernel's representation of a user thread, upon which the Oracle 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 defined in the system header file sys/procfs.h
and are used by observability and debugging tools such as ps, pgrep, and truss. For more information, see ps
(1), pgrep
(1), truss
(1), and proc
(5) man pages. The following table lists example expressions using curpsinfo
, their types, and their meanings:
Table 2-14 curpsinfo
in Expressions
Expression | Type | Description |
---|---|---|
|
|
Current process ID |
|
|
Executable file name |
|
|
Initial command line arguments |
You should review the complete structure definition later by examining the sys/procfs.h
header file. For more information, see proc
(5). 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 Oracle 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
driver and read
() requests. For more information, see the ksyms
(4D) man page. The driver makes use of two common structs, known as uio
and iovec
and, to respond to requests to read from the character device file /dev/ksyms
. For more information, see the uio
(9S) and iovec
(9S) man page.
The uio
struct, accessed using the name struct uio
or type alias uio_t
, is described in the uio
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
structures which each describe a piece of the requested I/O, in the event that multiple chunks are requested using the readv
or writev
system calls. For more information, see the readv
(2) and writev
(2) man page. One of the kernel device driver interface (DDI) routines that operates on struct uio
is the function uiomove
(), which is one of a family of functions kernel drivers use to respond to user process read
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
routine to respond to read
requests. The next example illustrates that the arguments and calls to read
from /dev/ksyms
match the calls by the driver to uiomove
to copy the results back into the user address space at the location specified to read
. For more information, see the uiomove
(9F) man page.
Use the strings -a
command to force a bunch of reads from /dev/ksyms
. Try running strings -a /dev/ksyms
in your shell and see what output it produces. For more information, see the strings
(1) man page.
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 the strings
command so that the script selects the correct read
requests before tracing the arguments. Notice that by using operator ==
with a left argument that is an array of char and a right argument that is a string, the D compiler infers that the left 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
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
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
(). 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
() and again save it in the file ksyms.d
:
Example 2-9 Tracing the read
and uiomove
() Relationship
/* * When 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
. 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 a 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
, respectively. These values should match the input parameters to the read
system call issued on the driver. For more information, see the read
(2) and uiomove
(9F) man pages. 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
match the parameters passed to uiomove
by the ksyms
driver.