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/
and pid
/psinfo/proc/
. The pid
/lwps/lwpid
/lwpsinfo/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:
|
|
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 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.