Solaris Dynamic Tracing Guide
Previous Next

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.

Previous Next