Output Formatting

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:

%d

Print the corresponding value as a decimal integer

%s

Print the corresponding value as a string

%x

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.