Chapter 9 Scripting

You can use the dtrace command to create interpreter files from D programs, which are similar to shell scripts that can be installed as reusable interactive DTrace tools. The D compiler and the dtrace command provide a set of macro variables that are expanded by the D compiler to make it easy to create DTrace scripts. This chapter provides a reference for the macro variable facility and tips for creating persistent scripts.

9.1 Interpreter Files

Similar to your shell and utilities such as awk and perl, you can use the dtrace command to create executable interpreter files.

An interpreter file begins with a line of the following form:

#!pathname [arg]

where pathname is the path of the interpreter and arg is a single, optional argument. When an interpreter file is executed, the system invokes the specified interpreter. If arg was specified in the interpreter file, it is passed as an argument to the interpreter. The path to the interpreter file and any additional arguments that were specified when it was executed are then appended to the interpreter argument list. Therefore, you always need to create DTrace interpreter files with at least the following arguments:

#!/usr/sbin/dtrace -s

When your interpreter file is executed, the argument to the -s option is the pathname of the interpreter file. The dtrace command then reads, compiles, and executes this file as if you had typed the following command in your shell:

# dtrace -s interpreter-file

The following example shows how you would create and execute a dtrace interpreter file. First, type the following D source code and save it in a file named interp.d:

#!/usr/sbin/dtrace -s
BEGIN
{
  trace("hello");
  exit(0);
}

Then, make the interp.d file executable and execute it as follows:

# chmod a+rx interp.d
# ./interp.d
dtrace: script './interp.d' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0      1                           :BEGIN   hello                            
#

Remember that the #! directive must comprise the first two characters of your file with no intervening or preceding white space. The D compiler automatically ignores this line when it processes the interpreter file.

The dtrace command uses getopt() to process command-line options so that you can combine multiple options in your single interpreter argument. For example, to add the -q option to the previous example you could change the interpreter directive to the following:

#!/usr/sbin/dtrace -qs
Note

If you specify multiple options, the -s option must always end the list of options so that the next argument, the interpreter file name, is correctly processed as the argument to the -s option.

If you need to specify more than one option that requires an argument in your interpreter file, use the #pragma D option directive to set your options. Several dtrace command-line options have #pragma equivalents that you can use. See Chapter 10, Options and Tunables.

9.2 Macro Variables

The D compiler defines a set of built-in macro variables that you can use when writing D programs or interpreter files. Macro variables are identifiers that are prefixed with a dollar sign ($) and are expanded once by the D compiler when processing your input file. The following table describes the macro variables that the D compiler provides.

Table 9.1 D Macro Variables

Name

Description

Reference

$[0-9]+

Macro arguments

Section 9.3, “Macro Arguments”

$egid

Effective group ID

See the getegid(2) manual page.

$euid

Effective user ID

See the geteuid(2) manual page.

$gid

Real group ID

See the getgid(2) manual page.

$pid

Process ID

See the getpid(2) manual page.

$pgid

Process group ID

See the getpgid(2) manual page.

$ppid

Parent process ID

See the getppid(2) manual page.

$sid

Session ID

See the getsid(2) manual page.

$target

Target process ID

Section 9.4, “Target Process ID”

$uid

Real user ID

See the getuid(2) manual page


With the exception of the $[0-9]+ macro arguments and the $target macro variable, all of the macro variables expand to integers that correspond to system attributes, such as the process ID and the user ID. The variables expand to the attribute value associated with the current dtrace process or whatever process is running the D compiler.

Using macro variables in interpreter files enables you to create persistent D programs that you do not need to edit every time you want to use them. For example, to count all system calls, except those that are executed by the dtrace command, you would use the following D program clause containing $pid:

syscall:::entry
/pid != $pid/
{
  @calls = count();
}

This clause always produces the desired result, even though each invocation of the dtrace command has a different process ID. Macro variables can be used in a D program anywhere that an integer, identifier, or string can be used.

Macro variables are expanded only one time when the input file is parsed, not recursively.

Except in probe descriptions, each macro variable is expanded to form a separate input token and cannot be concatenated with other text to yield a single token.

For example, if $pid expands to the value 456, the D code in the following example would expand to the two adjacent tokens 123 and 456, resulting in a syntax error, rather than the single integer token 123456:

123$pid

However, in probe descriptions, macro variables are expanded and concatenated with adjacent text. For example, the following clause uses the DTrace pid provider to instrument the dtrace command:

# dtrace -c ./a.out -n 'pid$target:libc.so::entry'

Macro variables are only expanded one time within each probe description field and they may not contain probe description delimiters (:).

9.3 Macro Arguments

The D compiler also provides a set of macro variables corresponding to any additional argument operands that are specified as part of the dtrace command invocation. These macro arguments are accessed by using the built-in names $0, for the name of the D program file or dtrace command, $1, for the first additional operand, $2 for the second operand, and so on. If you use the -s option, $0 expands to the value of the name of the input file that is used with this option. For D programs that are specified on the command line, $0 expands to the value of argv[0], which is used to execute the dtrace command itself.

Macro arguments can expand to integers, identifiers, or strings, depending on the form of the corresponding text. As with all macro variables, macro arguments can be used anywhere integer, identifier, and string tokens can be used in a D program.

All of the following examples could form valid D expressions assuming appropriate macro argument values:

execname == $1  /* with a string macro argument */

x += $1         /* with an integer macro argument */

trace(x->$1)    /* with an identifier macro argument */

Macro arguments can be used to create DTrace interpreter files that act like real Linux commands and use information that is specified by a user or by another tool to modify their behavior.

For example, the following D interpreter file traces write() system calls that are executed by a particular process ID and saved in a file named tracewrite:

#!/usr/sbin/dtrace -s 
syscall::write:entry
/pid == $1/
{
}

If you make this interpreter file executable, you can specify the value of $1 by using an additional command-line argument to your interpreter file, for example:

# chmod a+rx ./tracewrite
# ./tracewrite 12345

The resulting command invocation counts each write() system call that is executed by the process ID 12345.

If your D program references a macro argument that is not provided on the command line, an appropriate error message is printed and your program fails to compile, as shown in the following example:

# ./tracewrite
dtrace: failed to compile script ./tracewrite: line 4: 
  macro argument $1 is not defined

D programs can reference unspecified macro arguments if you set the defaultargs option. If defaultargs is set, unspecified arguments have the value 0. See Chapter 10, Options and Tunables for more information about D compiler options. The D compiler also produces an error message if additional arguments that are not referenced by your D program are specified on the command line.

The macro argument values must match the form of an integer, identifier, or string. If the argument does not match any of these forms, the D compiler reports an appropriate error message. When specifying string macro arguments to a DTrace interpreter file, you should surround the argument in an extra pair of single quotes to avoid interpretation of the double quotes and string contents by your shell:

# ./foo '"a string argument"'

If you want your D macro arguments to be interpreted as string tokens, even if they match the form of an integer or identifier, prefix the macro variable or argument name with two leading dollar signs, for example, $$1, which forces the D compiler to interpret the argument value as if it were a string surrounded by double quotes. All of the usual D string escape sequences, per Table 2.6, “Character Escape Sequences”, are expanded inside of any string macro arguments, regardless of whether they are referenced by using the $arg or $$arg form of the macro. If the defaultargs option is set, unspecified arguments that are referenced with the $$arg form have the value of the empty string ("").

9.4 Target Process ID

Use the $target macro variable to create scripts to be applied to the user process of interest that you specify with the -p option or that you create by using the dtrace command with the -c option. The D programs that you specify on the command line or by using the -s option are compiled after processes are created or grabbed, and the $target variable expands to the integer process ID of the first such process.

For example, you could use the following D script to determine the distribution of system calls that are executed by a particular subject process. Save it in a file named syscall.d:

syscall:::entry
/pid == $target/
{
  @[probefunc] = count();
}

To determine the number of system calls executed by the date command, save the script in the file named syscall.d, then run the following command:

# dtrace -s syscall.d -c date
dtrace: script 'syscall.d' matched 296 probes
Tue Oct 16 15:12:07 BST 2012

  access                                                            1
  arch_prctl                                                        1
  clock_gettime                                                     1
  exit_group                                                        1
  getrlimit                                                         1
  lseek                                                             1
  rt_sigprocmask                                                    1
  set_robust_list                                                   1
  set_tid_address                                                   1
  write                                                             1
  futex                                                             2
  rt_sigaction                                                      2
  brk                                                               3
  munmap                                                            3
  read                                                              5
  open                                                              6
  mprotect                                                          7
  close                                                             8
  newfstat                                                          8
  mmap                                                             16