System call tracing is a powerful way to observe the behavior of most user processes. If you've used the Solaris truss ( 1 ) utility before as an administrator or developer, you've probably learned that it's a useful tool to keep around for whenever there is a problem. If you've never used truss before, give it a try right now by typing this command into one of your shells:
$ truss date
You will see a formatted trace of all the system calls executed by
date
(
1
)
followed by its one line of output at the end. The following example improves upon the earlier rw.d
program by formatting its output to look more like
truss
(
1
)
so you can more easily understand the output. Type the following program and save it in a file called trussrw.d
:
Example 1.2.
trussrw.d
: Trace System Calls with
truss
(
1
)
Output Format
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); } syscall::read:return, syscall::write:return /pid == $1/ { printf("\t\t = %d\n", arg1); }
In this example, the constant 12345
is replaced with the label $1
in each predicate. This label allows you to specify the process of interest as an argument to the script: $1
is replaced by the value of the first argument when the script is compiled. To execute trussrw.d
, use the dtrace options
q
and
s
, followed by the process ID of your shell as the final argument. The
q
option indicates that dtrace should be quiet and suppress the header line and the CPU and ID columns shown in the preceding examples. As a result, you will only see the output for the data that you explicitly traced. Type the following command (replacing 12345
with the process ID of a shell process) and then press return a few times in the specified shell:
# dtrace -q -s trussrw.d 12345
= 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1) = 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1) = 1 write(2, 0x8089e48, 1) = 1 read(63, 0x8090a38, 1024) = 0 read(63, 0x8090a38, 1024) = 0 write(2, 0x8089e48, 52) = 52 read(0, 0x8089878, 1)^C
#
Now let's examine your D program and its output in more detail. First, a clause similar to the earlier program instruments each of the shell's calls to
read
(
2
)
and
write
(
2
)
. But for this example, a new function, printf
, is used to trace data and print it out in a specific format:
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); }
The printf
function combines the ability to trace data, as if by the trace
function used earlier, with the ability to output the data and other text in a specific format that you describe. The printf
function tells DTrace to trace the data associated with each argument after the first argument, and then to format the results using the rules described by the first printf
argument, known as a format string.
The format string is a regular string that contains any number of format conversions, each beginning with the %
character, that describe how to format the corresponding argument. The first conversion in the format string corresponds to the second printf
argument, the second conversion to the third argument, and so on. All of the text between conversions is printed verbatim. The character following the %
conversion character describes the format to use for the corresponding argument. Here are the meanings of the three format conversions used in trussrw.d
:
|
Print the corresponding value as a decimal integer |
|
Print the corresponding value as a string |
|
Print the corresponding value as a hexadecimal integer |
DTrace printf
works just like the C
printf
(
3C
)
library routine or the shell
printf
(
1
)
utility. If you've never seen printf
before, the formats and options are explained in detail in Chapter 12, Output Formatting. You should read this chapter carefully even if you're already familiar with printf
from another language. In D, printf
is provided as a built-in and some new format conversions are available to you designed specifically for DTrace.
To help you write correct programs, the D compiler validates each printf
format string against its argument list. Try changing probefunc
in the clause above to the integer 123
. If you run the modified program, you will see an error message telling you that the string format conversion %s
is not appropriate for use with an integer argument:
# dtrace -q -s trussrw.d
dtrace: failed to compile script trussrw.d: line 4: printf( )
argument #2 is incompatible with conversion #1 prototype:
conversion: %s
prototype: char [] or string (or use stringof)
argument: int
#
To print the name of the read or write system call and its arguments, use the printf
statement:
printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2);
to trace the name of the current probe function and the first three integer arguments to the system call, available in the DTrace variables arg0
, arg1
, and arg2
. For more information about probe arguments, see Chapter 3, Variables. The first argument to
read
(
2
)
and
write
(
2
)
is a file descriptor, printed in decimal. The second argument is a buffer address, formatted as a hexadecimal value. The final argument is the buffer size, formatted as a decimal value. The format specifier %4d
is used for the third argument to indicate that the value should be printed using the %d
format conversion with a minimum field width of 4 characters. If the integer is less than 4 characters wide, printf
will insert extra blanks to align the output.
To print the result of the system call and complete each line of output, use the following clause:
syscall::read:return, syscall::write:return /pid == $1/ { printf("\t\t = %d\n", arg1); }
Notice that the syscall
provider also publishes a probe named return
for each system call in addition to entry
. The DTrace variable arg1
for the syscall return
probes evaluates to the system call's return value. The return value is formatted as a decimal integer. The character sequences beginning with backwards slashes in the format string expand to tab (\t
) and newline (\n
) respectively. These escape sequences help you print or record characters that are difficult to type. D supports the same set of escape sequences as C, C++, and the Java programming language. The complete list of escape sequences is found in Chapter 2, Types, Operators, and Expressions.