Solaris 动态跟踪指南

第 15 章 脚本

您可以使用 dtrace(1M) 实用程序从 D 程序中创建与 shell 脚本类似的解释程序文件,这些文件可安装为可重用的交互式 DTrace 工具。D 编译器和 dtrace 命令提供一组由 D 编译器扩展的宏变量,使得创建 DTrace 脚本很容易。本章介绍对宏变量工具的引用和创建持久性脚本的技巧。

解释程序文件

与 shell 和实用程序(如 awk(1)perl(1))类似,可使用 dtrace(1M) 创建可执行的解释程序文件。解释程序文件的起始行格式如下所示:

#! pathname arg

其中,pathname 是解释程序的路径,arg 是单个可选参数。执行解释程序文件时,系统将调用指定的解释程序。如果解释程序文件中指定了 arg,则会将其作为参数传递到解释程序。解释程序文件本身的路径和执行该文件时指定的任何其他参数,随后将附加到解释程序参数列表。因此,将始终需要创建至少带有以下参数的 DTrace 解释程序文件:

#!/usr/sbin/dtrace -s

执行解释程序文件时,-s 选项的参数将成为解释程序文件本身的路径名。然后 dtrace 将读取、编译和执行此文件,作用与在 shell 中键入以下命令类似:


# dtrace -s interpreter-file

以下示例说明如何创建和执行 dtrace 解释程序文件。键入以下 D 源代码,并将其保存在名为 interp.d 的文件中:

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

interp.d 文件标记为可执行文件,并按以下方式执行该文件:


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

请记住,#! 指令必须是文件的前两个字符,中间或前面都不得有空格。D 编译器在处理解释程序文件时会自动忽略此行。

dtrace 使用 getopt(3C) 处理命令行选项,以便于将多个选项合并为单个解释程序参数。例如,要将 -q 选项添加到前面的示例中,可以将解释程序指令更改为:

#!/usr/sbin/dtrace -qs

如果指定多个选项字母,则必须始终用 -s 选项结束布尔选项列表,以便将下一个参数(解释程序文件名)作为与 -s 选项对应的参数进行处理。

如果需要在解释程序文件中指定多个要求参数的选项,则不能将所有选项和参数放入单个解释程序参数中。请改为使用 #pragma D option 指令语法设置这些选项。所有 dtrace 命令行选项都有可以使用的 #pragma 等效选项,如第 16 章中所示。

宏变量

D 编译器定义了一组内置的宏变量,可以在编写 D 程序或解释程序文件时使用这些变量。宏变量是以美元符号 ($) 作为前缀的标识符,并在处理输入文件时由 D 编译器扩展一次。D 编译器提供了以下宏变量:

表 15–1 D 宏变量

名称 

说明 

参考 

$[0-9]+

宏参数 

请参见宏参数

$egid

有效组 ID 

getegid(2)

$euid

有效用户 ID 

geteuid(2)

$gid

实际组 ID 

getgid(2)

$pid

进程 ID 

getpid(2)

$pgid

进程组 ID 

getpgid(2)

$ppid

父进程 ID 

getppid(2)

$projid

项目 ID 

getprojid(2)

$sid

会话 ID 

getsid(2)

$target

目标进程 ID 

请参见目标进程 ID

$taskid

任务 ID 

gettaskid(2)

$uid

实际用户 ID 

getuid(2)

除了 $[0-9]+ 宏参数和 $target 宏变量以外,所有宏变量都扩展为与系统属性(如进程 ID 和用户 ID)对应的整数。变量扩展为与当前 dtrace 进程本身或正在运行 D 编译器的任何进程关联的属性值。

通过在解释程序文件中使用宏变量,可以创建持久性的 D 程序,每次使用这些程序时无需对其进行编辑。例如,要统计除 dtrace 命令执行的系统调用以外的所有系统调用,可以使用包含 $pid 的以下 D 程序子句:

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

即使每次调用 dtrace 命令都将得到不同的进程 ID,此子句也始终会生成所需的结果。

D 程序中可使用整数、标识符或字符串的任何位置均可使用宏变量。在解析输入文件时,将只扩展宏变量一次(即,不递归扩展)。每个扩展的宏变量形成一个单独的输入标记,不能与其他文本串联生成单个标记。例如,如果 $pid 扩展为值 456,则 D 代码:

123$pid

将扩展为两个相邻的标记 123 和 456,这将导致语法错误,而不会生成单个整数标记 123456。

宏变量在程序子句的开始位置扩展,并与 D 探测器说明内部的相邻文本串联。例如,以下子句使用 DTrace pid 提供器检测 dtrace 命令:

pid$pid:libc.so:printf:entry
{
	...
}

宏变量在每个探测器说明字段中仅扩展一次;它们不得包含探测器说明分界符 (:)。

宏参数

D 编译器还提供了一组与任何附加参数操作数(指定为 dtrace 命令调用的一部分)对应的宏变量。可使用内置名称 $0 表示 D 程序文件的名称或 dtrace 命令、$1 表示第一个附加操作数、$2 表示第二个操作数,依此类推,来访问这些宏参数。如果使用了 dtrace -s 选项,则 $0 将扩展为与此选项配合使用的输入文件的名称的值。对于在命令行上指定的 D 程序,$0 扩展为用于执行 dtrace 本身的 argv[0] 的值。

宏参数可以扩展为整数、标识符或字符串,具体取决于相应文本的格式。与所有宏变量一样,D 程序中可使用整数、标识符和字符串标记的任何位置均可使用宏参数。以下所有示例都可以构成带有相应宏参数值的有效 D 表达式:

execname == $1    /* with a string macro argument */
x += $1           /* with an integer macro argument */
trace(x->$1)      /* with an identifier macro argument */

宏参数可用于创建 dtrace 解释程序文件,这些文件的作用就像实际的 Solaris 命令,并且使用用户或其他工具指定的信息来修改其行为。例如,以下 D 解释程序文件跟踪按特定进程 ID 执行的 write(2) 系统调用:

#!/usr/sbin/dtrace -s

syscall::write:entry
/pid == $1/
{
}

如果将此解释程序文件设置为可执行文件,则可以使用解释程序文件的附加命令行参数来指定 $1 的值:


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

产生的命令调用统计由进程 ID 12345 执行的每个 write(2) 系统调用。

如果 D 程序引用的宏参数不是在命令行上提供的,则会列显相应的错误消息,且程序将编译失败:


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

如果设置了 defaultargs 选项,则 D 程序可以引用未指定的宏参数。如果设置 defaultargs,则未指定的参数的值将为 0。有关 D 编译器选项的更多信息,请参见第 16 章。如果在命令行上指定了 D 程序未引用的附加参数,则 D 编译器也将生成错误消息。

宏参数值必须与整数、标识符或字符串的格式匹配。如果参数与这些格式中的任何一种都不匹配,则 D 编译器将报告相应的错误消息。将字符串宏参数指定到 DTrace 解释程序文件时,使用一对额外的单引号将该参数引起来可避免 shell 解释双引号和字符串内容:


# ./foo '"a string argument"'

如果要将 D 宏参数解释为字符串标记(即使这些参数与整数或标识符的格式匹配),请使用两个前导美元符号(如 $$1)作为宏变量或参数名称的前缀,以强制 D 编译器解释参数值(就像参数值是由双引号引起来的字符串一样)。无论是使用 $arg 还是 $$arg 格式的宏来引用 D 字符串转义序列,任何字符串宏参数中的所有常用 D 字符串转义序列(请参见表 2–5)都将被扩展。如果已设置 defaultargs 选项,则使用 $$arg 格式引用的未指定参数的值为空字符串 ("")。

目标进程 ID

使用 $target 宏变量可创建脚本,这些脚本可应用于所关注的特定用户进程(在 dtrace 命令行上使用 -p 选项选择的进程或使用 -c 选项创建的进程)。在创建或抓取进程且 $target 变量扩展为第一个此类进程的整数进程 ID ,将编译在命令行上指定或使用 -s 选项指定的 D 程序。例如,以下 D 脚本可用于确定由特定主题进程执行的系统调用的分布:

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

要确定由 date(1) 命令执行的系统调用的数量,请将脚本保存在文件 syscall.d 中并执行以下命令:


# dtrace -s syscall.d -c date
dtrace: script 'syscall.d' matched 227 probes
Fri Jul 30 13:46:06 PDT 2004
dtrace: pid 109058 has exited

  gtime                                                             1
  getpid                                                            1
  getrlimit                                                         1
  rexit                                                             1
  ioctl                                                             1
  resolvepath                                                       1
  read                                                              1
  stat                                                              1
  write                                                             1
  munmap                                                            1
  close                                                             2
  fstat64                                                           2
  setcontext                                                        2
  mmap                                                              2
  open                                                              2
  brk                                                               4