Solaris 动态跟踪指南

第 10 章 操作和子例程

您可以使用 D 函数调用(如 trace()printf())来调用由 DTrace 提供的两种不同类型的服务:操作,用于跟踪数据或修改 DTrace 外部状态;子例程,仅影响内部 DTrace 状态。本章定义操作和子例程,并说明它们的语法和语义。

操作

通过操作可将 DTrace 程序与 DTrace 外部的系统交互。大多数常见操作将数据记录到 DTrace 缓冲区。也有其他可用操作,例如停止当前进程,提高当前进程的特定信号或停止全部跟踪。这些操作中的一些具有破坏性,因为它们会更改系统,虽然这些操作具有可靠的定义。只有显式启用了破坏性的操作,才可以使用这些操作。缺省情况下,数据记录操作将数据记录到主体缓冲区。有关主体缓冲区和缓冲区策略的详细信息,请参见第 11 章

缺省操作

子句可以包含任意数量的操作和变量处理。如果将子句保留为空,则会采用缺省操作。缺省操作将跟踪主体缓冲区已启用的探测器标识符 (enabled probe identifier, EPID)。EPID 使用特定谓词和操作标识特定探测器的特定启用。通过 EPID,DTrace 使用者可以确定引起该操作的探测器。实际上,只要跟踪数据,都必须为该数据附加 EPID,以便于使用者理解数据。这样,缺省操作将跟踪 EPID,而不会执行任何其他任务。

使用缺省操作可以使 dtrace(1M) 的用法很简单。例如,以下示例命令使用缺省操作启用 TS 分时调度模块中的所有探测器:


# dtrace -m TS

上面的命令可能生成与以下示例类似的输出:


# dtrace -m TS
dtrace: description 'TS' matched 80 probes
CPU     ID                    FUNCTION:NAME
  0  12077                 ts_trapret:entry 
  0  12078                ts_trapret:return 
  0  12069                   ts_sleep:entry 
  0  12070                  ts_sleep:return 
  0  12033                  ts_setrun:entry 
  0  12034                 ts_setrun:return 
  0  12081                  ts_wakeup:entry 
  0  12082                 ts_wakeup:return 
  0  12069                   ts_sleep:entry 
  0  12070                  ts_sleep:return 
  0  12033                  ts_setrun:entry 
  0  12034                 ts_setrun:return 
  0  12069                   ts_sleep:entry 
  0  12070                  ts_sleep:return 
  0  12033                  ts_setrun:entry 
  0  12034                 ts_setrun:return 
  0  12069                   ts_sleep:entry 
  0  12070                  ts_sleep:return 
  0  12023                  ts_update:entry 
  0  12079             ts_update_list:entry 
  0  12080            ts_update_list:return 
  0  12079             ts_update_list:entry 
...

数据记录操作

数据记录操作包含核心 DTrace 操作。缺省情况下,这些操作中的每一个都会将数据记录到主体缓冲区,但每个操作也可以用于将数据记录到推理缓冲区。有关主体缓冲区的更多详细信息,请参见第 11 章。有关推理缓冲区的更多详细信息,请参见第 13 章。本节中的说明仅是指定向缓冲区,表示如果操作执行 speculate(),数据将记录到主体缓冲区或推理缓冲区。

trace()

void trace(expression)

最基本的操作是 trace() 操作,此操作将 D 表达式用作其参数并跟踪结果到定向缓冲区。以下语句为 trace() 操作的示例:

trace(execname);
trace(curlwpsinfo->pr_pri);
trace(timestamp / 1000);
trace(`lbolt);
trace("somehow managed to get here");

tracemem()

void tracemem(address, size_t nbytes)

tracemem() 操作将 D 表达式用作其第一个参数 address,将常量用作其第二个参数 nbytestracemem()addr 指定的地址复制 nbytes 指定长度的内存到定向缓冲区中。

printf()

void printf(string format, ...) 

trace() 一样,printf() 操作将跟踪 D 表达式。但是,printf() 允许详细设置 printf(3C) 样式的格式。与 printf(3C) 一样,参数包含 format 字符串,后跟任意数量的参数。缺省情况下,将跟踪参数到定向缓冲区。然后,dtrace(1M) 将根据指定的格式字符串对参数进行格式化,以便输出。例如,可将 trace() 中的前两个 trace() 示例组合到一个 printf() 中:

printf("execname is %s; priority is %d", execname, curlwpsinfo->pr_pri);

有关 printf() 的更多信息,请参见第 12 章

printa()

void printa(aggregation)
void printa(string format, aggregation)

使用 printa() 操作可以显示和格式化聚合。有关聚合的更多详细信息,请参见第 9 章。如果未提供 formatprinta() 将仅跟踪指示使用缺省格式处理和显示指定聚合的 DTrace 使用者指令。如果提供了 format,将按照指定的格式对聚合进行格式设置。有关 printa() 格式字符串的更详细说明,请参见第 12 章

printa() 仅跟踪指示应由 DTrace 使用者处理聚合的指令。它不会处理内核中的聚合。因此,跟踪 printa() 指令和实际处理指令之间的时间取决于影响缓冲区处理的因素。这些因素包括聚合速率、缓冲策略,以及切换缓冲区的速率(如果缓冲策略为 switching)。有关这些因素的详细说明,请参见第 9 章第 11 章

stack()

void stack(int nframes)
void stack(void)

stack() 操作会将内核栈跟踪记录到定向缓冲区。内核栈的深度将为 nframes。如果未提供 nframes,则记录的栈帧的数目即为 stackframes 选项指定的数目。例如:


# dtrace -n uiomove:entry'{stack()}'
  CPU     ID                    FUNCTION:NAME
    0   9153                    uiomove:entry 
                genunix`fop_write+0x1b
                namefs`nm_write+0x1d
                genunix`fop_write+0x1b
                genunix`write+0x1f7

    0   9153                    uiomove:entry 
                genunix`fop_read+0x1b
                genunix`read+0x1d4

    0   9153                    uiomove:entry 
                genunix`strread+0x394
                specfs`spec_read+0x65
                genunix`fop_read+0x1b
                genunix`read+0x1d4
   ...

stack() 操作与其他操作有些不同,该操作还可以用作聚合的关键字:


# dtrace -n kmem_alloc:entry'{@[stack()] = count()}'
dtrace: description 'kmem_alloc:entry' matched 1 probe
^C

                rpcmod`endpnt_get+0x47c
                rpcmod`clnt_clts_kcallit_addr+0x26f
                rpcmod`clnt_clts_kcallit+0x22
                nfs`rfscall+0x350
                nfs`rfs2call+0x60
                nfs`nfs_getattr_otw+0x9e
                nfs`nfsgetattr+0x26
                nfs`nfs_getattr+0xb8
                genunix`fop_getattr+0x18
                genunix`cstat64+0x30
                genunix`cstatat64+0x4a
                genunix`lstat64+0x1c
                  1

                genunix`vfs_rlock_wait+0xc
                genunix`lookuppnvp+0x19d
                genunix`lookuppnat+0xe7
                genunix`lookupnameat+0x87
                genunix`lookupname+0x19
                genunix`chdir+0x18
                  1

                rpcmod`endpnt_get+0x6b1
                rpcmod`clnt_clts_kcallit_addr+0x26f
                rpcmod`clnt_clts_kcallit+0x22
                nfs`rfscall+0x350
                nfs`rfs2call+0x60
                nfs`nfs_getattr_otw+0x9e
                nfs`nfsgetattr+0x26
                nfs`nfs_getattr+0xb8
                genunix`fop_getattr+0x18
                genunix`cstat64+0x30
                genunix`cstatat64+0x4a
                genunix`lstat64+0x1c
                  1
    ...

ustack()

void ustack(int nframes, int strsize)
void ustack(int nframes)
void ustack(void)

ustack() 操作会将用户栈跟踪记录到定向缓冲区中。用户栈的深度将为 nframes。如果未提供 nframes,则记录的栈帧的数目即为 ustackframes 选项指定的数目。虽然触发探测器时,ustack() 能够确定调用帧的地址,但 DTrace 使用者在用户级处理 ustack() 操作之前,栈帧将不会转换为符号。如果指定了 strsize 且它不为零,则 ustack() 将分配指定的字符串空间量,并使用该空间直接从内核中执行地址到符号的转换。这种直接的用户符号转换当前仅在版本 1.5 和更高版本的 Java 虚拟机中可用。Java 地址到符号转换使用 Java 类和方法名注释包含 Java 帧的用户栈。如果无法转换这类帧,帧将仅显示为十六进制地址。

以下示例跟踪不包含字符串空间的栈,因此将不会进行 Java 地址到符号的转换:


# dtrace -n syscall::write:entry'/pid == $target/{ustack(50, 0); 
    exit(0)}' -c "java -version"
dtrace: description 'syscall::write:entry' matched 1 probe
java version "1.5.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta3-b58)
Java HotSpot(TM) Client VM (build 1.5.0-beta3-b58, mixed mode)
dtrace: pid 5312 has exited
CPU     ID                    FUNCTION:NAME
  0     35                      write:entry 
              libc.so.1`_write+0x15
              libjvm.so`__1cDhpiFwrite6FipkvI_I_+0xa8
              libjvm.so`JVM_Write+0x2f
              d0c5c946
              libjava.so`Java_java_io_FileOutputStream_writeBytes+0x2c
              cb007fcd
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb002a7b
              cb000152
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_
                          pnGThread__v_+0x187
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_
                          pnGThread__v2468_v_+0x14
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle_
                          pnRJavaCallArguments_pnGThread __v_+0x28
              libjvm.so`__1cRjni_invoke_static6FpnHJNIEnv__pnJJavaValue_
                          pnI_jobject_nLJNICallType_pnK_jmethodID_pnSJNI_
                          ArgumentPusher_pnGThread__v_+0x180
              libjvm.so`jni_CallStaticVoidMethod+0x10f
              java`main+0x53d

请注意,Java 虚拟机中的 C 和 C++ 栈帧使用 C++ 的“损坏”符号名称以符号形式显示,Java 栈帧仅显示为十六进制地址。以下示例说明了对包含非零字符串空间的 ustack() 的调用:


# dtrace -n syscall::write:entry'/pid == $target/{ustack(50, 500); exit(0)}'
      -c "java -version"
dtrace: description 'syscall::write:entry' matched 1 probe
java version "1.5.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta3-b58)
Java HotSpot(TM) Client VM (build 1.5.0-beta3-b58, mixed mode)
dtrace: pid 5308 has exited
CPU     ID                    FUNCTION:NAME
  0     35                      write:entry 
              libc.so.1`_write+0x15
              libjvm.so`__1cDhpiFwrite6FipkvI_I_+0xa8
              libjvm.so`JVM_Write+0x2f
              d0c5c946
              libjava.so`Java_java_io_FileOutputStream_writeBytes+0x2c
              java/io/FileOutputStream.writeBytes
              java/io/FileOutputStream.write
              java/io/BufferedOutputStream.flushBuffer
              java/io/BufferedOutputStream.flush
              java/io/PrintStream.write
              sun/nio/cs/StreamEncoder$CharsetSE.writeBytes
              sun/nio/cs/StreamEncoder$CharsetSE.implFlushBuffer
              sun/nio/cs/StreamEncoder.flushBuffer
              java/io/OutputStreamWriter.flushBuffer
              java/io/PrintStream.write
              java/io/PrintStream.print
              java/io/PrintStream.println
              sun/misc/Version.print
              sun/misc/Version.print
              StubRoutines (1)
              libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_pnGThread
                          __v_+0x187
              libjvm.so`__1cCosUos_exception_wrapper6FpFpnJJavaValue_
                          pnMmethodHandle_pnRJavaCallArguments_pnGThread
                          __v2468_v_+0x14
              libjvm.so`__1cJJavaCallsEcall6FpnJJavaValue_nMmethodHandle
                          _pnRJavaCallArguments_pnGThread__v_+0x28
              libjvm.so`__1cRjni_invoke_static6FpnHJNIEnv__pnJJavaValue_pnI
                          _jobject_nLJNICallType_pnK_jmethodID_pnSJNI
                          _ArgumentPusher_pnGThread__v_+0x180
              libjvm.so`jni_CallStaticVoidMethod+0x10f
              java`main+0x53d
              8051b9a

以上示例的输出演示了 Java 栈帧的符号栈帧信息。该输出中仍然有一些十六进制帧,这是因为一些函数为静态函数,并在应用程序符号表中没有条目。无法对这些帧进行转换。

非 Java 帧的 ustack() 符号转换发生在记录栈数据之后。因此在执行符号转换之前,对应的用户进程可能会退出,这将使栈帧转换无法进行。如果用户进程在执行符号转换之前退出,则 dtrace 将发出一条警告消息,后跟十六进制栈帧,如下例所示:


  dtrace: failed to grab process 100941: no such process
                c7b834d4
                c7bca85d
                c7bca1a4
                c7bd4374
                c7bc2628
                8047efc

第 33 章中介绍了减小此问题影响的方法。

最后,由于事后 DTrace 调试器命令无法执行帧转换,因此将 ustack()ring 缓冲区策略同时使用总是会生成原始 ustack() 数据。

以下 D 程序显示了未指定 strsizeustack() 示例:

syscall::brk:entry
/execname == $$1/
{
	@[ustack(40)] = count();
}

要在 Netscape Web 浏览器 .netscape.bin(缺省 Solaris 安装)中运行此示例,请使用以下命令:


# dtrace -s brk.d .netscape.bin
dtrace: description 'syscall::brk:entry' matched 1 probe
^C
                libc.so.1`_brk_unlocked+0xc
                88143f6
                88146cd
                .netscape.bin`unlocked_malloc+0x3e
                .netscape.bin`unlocked_calloc+0x22
                .netscape.bin`calloc+0x26
                .netscape.bin`_IMGCB_NewPixmap+0x149
                .netscape.bin`il_size+0x2f7
                .netscape.bin`il_jpeg_write+0xde
                8440c19
                .netscape.bin`il_first_write+0x16b
                8394670
                83928e5
                .netscape.bin`NET_ProcessHTTP+0xa6
                .netscape.bin`NET_ProcessNet+0x49a
                827b323
                libXt.so.4`XtAppProcessEvent+0x38f
                .netscape.bin`fe_EventLoop+0x190
                .netscape.bin`main+0x1875
                   1

                libc.so.1`_brk_unlocked+0xc
                libc.so.1`sbrk+0x29
                88143df
                88146cd
                .netscape.bin`unlocked_malloc+0x3e
                .netscape.bin`unlocked_calloc+0x22
                .netscape.bin`calloc+0x26
                .netscape.bin`_IMGCB_NewPixmap+0x149
                .netscape.bin`il_size+0x2f7
                .netscape.bin`il_jpeg_write+0xde
                8440c19
                .netscape.bin`il_first_write+0x16b
                8394670
                83928e5
                .netscape.bin`NET_ProcessHTTP+0xa6
                .netscape.bin`NET_ProcessNet+0x49a
                827b323
                libXt.so.4`XtAppProcessEvent+0x38f
                .netscape.bin`fe_EventLoop+0x190
                .netscape.bin`main+0x1875
                  1
    ...

jstack()

void jstack(int nframes, int strsize)
void jstack(int nframes)
void jstack(void)

jstack()ustack() 的别名,它为栈帧的数量使用 jstackframes 选项指定的值,为字符串空间大小使用 jstackstrsize 选项指定的值。缺省情况下,jstacksize 缺省为非零值。这意味着,使用 jstack() 将导致栈在原位置进行 Java 帧转换。

破坏性操作

一些 DTrace 操作具有破坏性,因为它们会以某种明确定义的方式更改系统的状态。如果未显式启用,则无法使用这些破坏性操作。使用 dtrace(1M) 时,可以使用 -w 选项启用破坏性操作。如果尝试在 dtrace(1M) 中启用破坏性操作,但不显式启用这些操作,则 dtrace 将失败,并显示一条与以下示例类似的消息:


dtrace: failed to enable 'syscall': destructive actions not allowed

处理破坏性操作

一些破坏性操作仅对特定进程具有破坏性。具有 dtrace_procdtrace_user 权限的用户可以使用这些操作。有关 DTrace 安全权限的详细信息,请参见第 35 章

stop()

void stop(void)

当触发已启用的探测器的进程接下来退出内核时,stop() 操作将强制停止该进程,就像由 proc(4) 操作停止一样。prun(1) 实用程序可用于恢复由 stop() 操作停止的进程。stop() 操作可用于在任何 DTrace 探测器位置停止进程。此操作可用于在特定状态下,捕获难以使用简单断点获取的程序,然后将一个传统调试器(如 mdb(1))附加至该进程。您也可以使用 gcore(1) 实用程序将已停止进程的状态保存在一个核心转储文件中,供以后进行分析。

raise()

void raise(int signal)

raise() 操作将指定的信号发送至当前正在运行的进程。此操作与使用 kill(1) 命令向进程发送信号类似。raise() 操作可用于在进程执行过程中的一个准确时间点发送信号。

copyout()

void copyout(void *buf, uintptr_t addr, size_t nbytes)

copyout() 操作在与当前线程关联的进程的地址空间中,从 buf 指定的缓冲区中复制 nbytesaddr 指定的地址。如果用户空间地址与当前地址空间中有效的错误页面不对应,则将生成错误。

copyoutstr()

void copyoutstr(string str, uintptr_t addr, size_t maxlen)

copyoutstr() 操作在与当前线程关联的进程的地址空间中,将 str 指定的字符串复制到 addr 指定的地址。如果用户空间地址与当前地址空间中有效的错误页面不对应,则将生成错误。字符串长度限于 strsize 选项指定的值。有关详细信息,请参见第 16 章

system()

void system(string program, ...) 

system() 操作将导致执行 program 指定的程序,就像将该程序作为输入提供给 shell 一样。program 字符串可以包含任何 printf()/printa() 格式转换。指定的参数必须与格式转换匹配。有关有效的格式转换的详细信息,请参阅第 12 章

以下示例每秒运行 date(1) 命令一次:


# dtrace -wqn tick-1sec'{system("date")}'
 Tue Jul 20 11:56:26 CDT 2004
 Tue Jul 20 11:56:27 CDT 2004
 Tue Jul 20 11:56:28 CDT 2004
 Tue Jul 20 11:56:29 CDT 2004
 Tue Jul 20 11:56:30 CDT 2004

以下示例通过在 program 字符串中使用 printf() 转换,同时使用传统过滤工具(如管道),更详细地说明了该操作的用法。

#pragma D option destructive
#pragma D option quiet

proc:::signal-send
/args[2] == SIGINT/
{
	printf("SIGINT sent to %s by ", args[1]->pr_fname);
	system("getent passwd %d | cut -d: -f5", uid);
}

运行上面的脚本将会生成与以下示例类似的输出:


# ./whosend.d
SIGINT sent to MozillaFirebird- by Bryan Cantrill
SIGINT sent to run-mozilla.sh by Bryan Cantrill
^C
SIGINT sent to dtrace by Bryan Cantrill

在触发探测器的上下文中不会执行指定的命令,此命令是在用户级处理包含 system() 操作的详细信息的缓冲区时执行的。进行此处理的方式和时间取决于第 11 章中介绍的缓冲策略。使用缺省缓冲策略时,缓冲区处理速率由 switchrate 选项指定。如果将 switchrate 显式地调整到高于其缺省值(一秒),则将会看到 system() 中的固有延迟,如下例所示:

#pragma D option quiet
#pragma D option destructive
#pragma D option switchrate=5sec

tick-1sec
/n++ < 5/
{
	printf("walltime  : %Y\n", walltimestamp);
	printf("date      : ");
	system("date");
	printf("\n");
}

tick-1sec
/n == 5/
{
	exit(0);
}

运行上面的脚本将会生成与以下示例类似的输出:


# dtrace -s ./time.d
 walltime  : 2004 Jul 20 13:26:30
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:31
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:32
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:33
date      : Tue Jul 20 13:26:35 CDT 2004

walltime  : 2004 Jul 20 13:26:34
date      : Tue Jul 20 13:26:35 CDT 2004

请注意,walltime 值会不同,但 date 值相同。此结果反映了一个事实,即仅当处理缓冲区而不是记录 system() 操作时,才会执行 date(1) 命令。

内核破坏性操作

一些破坏性操作对整个系统都具有破坏性。由于这些操作将会影响系统中的每个进程,以及任何其他隐式或显式依赖于受影响系统的网络服务的系统,因此使用这些操作时必须非常小心。

breakpoint()

void breakpoint(void)

breakpoint() 操作会引起内核断点,从而使系统停止并将控制转移到内核调试器。内核调试器将发出一个字符串,表示触发该操作的 DTrace 探测器。例如,如果要执行以下操作:


# dtrace -w -n clock:entry'{breakpoint()}'
dtrace: allowing destructive actions
dtrace: description 'clock:entry' matched 1 probe

在 SPARC 上运行的 Solaris 中,可能会在控制台上显示以下消息:


dtrace: breakpoint action at probe fbt:genunix:clock:entry (ecb 30002765700)
Type  'go' to resume
ok

在 x86 上运行的 Solaris 中,可能会在控制台上显示以下消息:


dtrace: breakpoint action at probe fbt:genunix:clock:entry (ecb d2b97060)
stopped at      int20+0xb:      ret
kmdb[0]:

探测器说明后面的地址是 DTrace 中启用控制块 (enabling control block, ECB) 的地址。您可以使用此地址确定有关引起断点操作的探测器启用的更多详细信息。

breakpoint() 操作中的错误可能会导致调用此操作的次数远远多于预期情况。此行为可能反过来阻止您终止正在触发断点操作的 DTrace 使用者。在此情况下,请将内核整数变量 dtrace_destructive_disallow 设置为 1。此设置将禁止计算机上的所有破坏性操作。在此特定情况下才应用此设置。

设置 dtrace_destructive_disallow 的准确方法将取决于您所使用的内核调试器。如果在 SPARC 系统上使用 OpenBoot PROM,请使用 w!


ok 1 dtrace_destructive_disallow w!
ok

确认是否已使用 w? 设置了改变量:


ok dtrace_destructive_disallow w?
1
ok

键入 go 以继续:


ok go

如果在 x86 或 SPARC 系统上使用 kmdb(1),请将 4 字节的写入修饰符 (W) 与 / 格式设置 dcmd 一起使用:


kmdb[0]: dtrace_destructive_disallow/W 1
dtrace_destructive_disallow:    0x0             =       0x1
kmdb[0]:

继续使用 :c


kadb[0]: :c

要在继续之后重新启用破坏性操作,需要使用 mdb(1)dtrace_destructive_disallow 显式重置为 0:


# echo "dtrace_destructive_disallow/W 0" | mdb -kw
dtrace_destructive_disallow:    0x1             =       0x0
#

panic()

void panic(void)

触发 panic() 操作将会导致内核出现紧急情况。此操作应该用于在重要时间强制进行系统故障转储。您可以将此操作与环形缓冲和事后分析结合使用来理解问题。有关更多信息,请分别参见第 11 章第 37 章。使用紧急情况操作时,将会显示紧急情况消息,表示导致该紧急情况的探测器。例如:


  panic[cpu0]/thread=30001830b80: dtrace: panic action at probe
  syscall::mmap:entry (ecb 300000acfc8)

  000002a10050b840 dtrace:dtrace_probe+518 (fffe, 0, 1830f88, 1830f88,
    30002fb8040, 300000acfc8)
    %l0-3: 0000000000000000 00000300030e4d80 0000030003418000 00000300018c0800
    %l4-7: 000002a10050b980 0000000000000500 0000000000000000 0000000000000502
  000002a10050ba30 genunix:dtrace_systrace_syscall32+44 (0, 2000, 5,
    80000002, 3, 1898400)
    %l0-3: 00000300030de730 0000000002200008 00000000000000e0 000000000184d928
    %l4-7: 00000300030de000 0000000000000730 0000000000000073 0000000000000010

  syncing file systems... 2 done
  dumping to /dev/dsk/c0t0d0s1, offset 214827008, content: kernel
  100% done: 11837 pages dumped, compression ratio 4.66, dump
  succeeded
  rebooting...

syslogd(1M) 还将在重新引导时发出一条消息:


  Jun 10 16:56:31 machine1 savecore: [ID 570001 auth.error] reboot after panic:
  dtrace: panic action at probe syscall::mmap:entry (ecb 300000acfc8)

故障转储的消息缓冲区还包含负责 panic() 操作的探测器和 ECB。

chill()

void chill(int nanoseconds)

chill() 操作将导致 DTrace 显示指定的纳秒数。chill() 主要用于查找可能与计时相关的问题。例如,您可以使用此操作打开竞争情况窗口,或者使定期事件相互之间同步或异步。由于处于 DTrace 探测器上下文中时禁止中断,因此使用任何 chill() 都将引起中断延迟、调度延迟和分发延迟。因此,chill() 可能会导致意外的系统影响,不应毫无限制地使用。由于系统活动依赖于定期的中断处理,如果在任何给定 CPU 的每一秒间隔内,chill() 操作需要的时间超过 500 毫秒,DTrace 将拒绝执行。如果超过最大 chill() 间隔,DTrace 将报告非法的操作错误,如下例所示:


# dtrace -w -n syscall::open:entry'{chill(500000001)}'
dtrace: allowing destructive actions
dtrace: description 'syscall::open:entry' matched 1 probe
dtrace: 57 errors
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 1 (ID 14: syscall::open:entry): \
  illegal operation in action #1

即使时间跨越多个 chill() 调用或者单个探测器的多个 DTrace 使用者,仍将会强制执行此限制。例如,以下命令将会生成相同的错误:


# dtrace -w -n syscall::open:entry'{chill(250000000); chill(250000001);}'

特殊操作

本节说明的操作既不是数据记录操作,也不是破坏性操作。

推理操作

与推理跟踪关联的操作为 speculate()commit()discard()。这些操作将在第 13 章中讨论。

exit()

void exit(int status)

exit() 操作用于立即停止跟踪,以及通知 DTrace 使用者应停止跟踪、执行任何最终处理并使用指定的状态调用 exit(3C)。因为 exit() 将状态返回至用户级,所以它是数据记录操作,但与其他数据存储操作不同,不能推理跟踪 exit()。无论缓冲策略是什么,exit() 都会导致 DTrace 使用者退出。由于 exit() 是数据记录操作,因此可以删除该操作。

调用 exit() 时,仅会完成其他 CPU 中已在进行中的 DTrace 操作。在任何 CPU 中都不会进行新的操作。对于此规则,唯一的例外是 END 探测器的处理,该探测器在 DTrace 使用者已处理了 exit() 操作,并指示应停止跟踪时调用。

子例程

子例程与操作不同,因为子例程通常仅会影响 DTrace 的内部状态。因此,没有破坏性的子例程,并且子例程从不会跟踪数据到缓冲区中。在 9F 或 3C 一节的接口中,许多子例程都有相应的模拟程序。有关相应子例程的更多信息,请参见 Intro(9F)Intro(3)

alloca()

void *alloca(size_t size)

alloca() 在临时空间外部分配 size 个字节,并返回指向已分配内存的指针。返回的指针一定是每 8 个字节对齐的。临时空间仅在子句的持续时间中有效。使用 alloca() 分配的内存在子句完成时将解除分配。如果没有可用的足够临时空间,将不会分配内存,并且将生成错误。

basename()

string basename(char *str)

basename()basename(1) 的 D 类似程序。此子例程创建一个包含所指定字符串的副本的字符串,但该字符串不包含以 / 结尾的任何前缀。返回的字符串分配到临时内存外部,因此它们仅在子句的持续时间中有效。如果没有可用的足够临时空间,则不会执行 basename,并且将生成错误。

bcopy()

void bcopy(void *src, void *dest, size_t size)

bcopy()src 指向的内存复制 size 字节到 dest 指向的内存。所有源内存都必须位于临时内存外部,所有目标内存都必须位于临时内存中。如果不满足这些条件,则不会进行复制操作,并且将生成错误。

cleanpath()

string cleanpath(char *str)

cleanpath() 创建包含由 str 表示的路径副本的字符串,但该字符串删除了某些多余元素。需要特别指出的是,路径中的 "/./" 元素已删除,并且 "/../" 元素已折叠。折叠路径中的 /../ 元素时将不考虑符号链接。因此,cleanpath() 可能会采用有效路径,但返回更短的无效路径。

例如,如果 str 为 "/foo/../bar" 且 /foo 为指向 /net/foo/export 的符号链接,则 cleanpath() 将返回字符串 "/bar",即使 bar 可能仅位于 /net/foo 中而不是 / 中。作出此限制是因为,cleanpath() 在触发探测器的上下文中调用,在此情况下不能进行完整的符号链接解析或使用任意名称。返回的字符串分配到临时内存外部,因此它们仅在子句的持续时间中有效。如果没有可用的足够临时空间,则不会执行 cleanpath,并且将生成错误。

copyin()

void *copyin(uintptr_t addr, size_t size)

copyin() 从指定的用户地址中复制指定的字节大小到 DTrace 临时缓冲区中,并返回此缓冲区的地址。用户地址解释为与当前线程关联的进程空间中的地址。生成的缓冲区指针一定是每 8 个字节对齐的。有问题的地址必须与当前进程中的错误页面对应。如果地址与错误页面不对应,或者没有可用的足够临时空间,则会返回 NULL,并且将生成错误。有关降低出现 copyin 错误可能性的方法,请参见第 33 章

copyinstr()

string copyinstr(uintptr_t addr)

copyinstr() 从指定的用户地址中复制以 null 结尾的 C 字符串到 DTrace 临时缓冲区中,并返回此缓冲区的地址。用户地址解释为与当前线程关联的进程空间中的地址。字符串长度限于由 strsize 选项指定的值;有关详细信息,请参见第 16 章。与 copyin 一样,指定的地址必须与当前进程中的错误页面对应。如果地址与错误页面不对应,或者没有可用的足够临时空间,则会返回 NULL,并且将生成错误。有关降低出现 copyinstr 错误可能性的方法,请参见第 33 章

copyinto()

void copyinto(uintptr_t addr, size_t size, void *dest)

copyinto() 从指定的用户地址中复制指定的字节大小到 dest 指定的 DTrace 临时缓冲区中。用户地址解释为与当前线程关联的进程空间中的地址。有问题的地址必须与当前进程中的错误页面对应。如果地址与错误页面不对应,或者任何目标内存位于临时空间外部,则不会进行复制操作,并且将生成错误。有关降低出现 copyinto 错误可能性的方法,请参见第 33 章

dirname()

string dirname(char *str)

dirname()dirname(1) 的 D 类似程序。此子例程创建一个字符串,该字符串包含除 str 指定的最后一级路径名之外的所有内容。返回的字符串分配到临时内存外部,因此它们仅在子句的持续时间中有效。如果没有可用的足够临时内存,则不会执行 dirname,并且将生成错误。

msgdsize()

size_t msgdsize(mblk_t *mp)

msgdsize() 返回由 mp 指向的数据消息中的字节数。有关详细信息,请参见 msgdsize(9F)msgdsize() 在计数中仅包括 M_DATA 类型的数据块。

msgsize()

size_t msgsize(mblk_t *mp)

msgsize() 返回由 mp 指向的消息中的字节数。与 msgdsize()(仅返回数据字节数)不同,msgsize() 返回消息中字节的总数

mutex_owned()

int mutex_owned(kmutex_t *mutex)

mutex_owned()mutex_owned(9F) 的实现。如果调用线程当前拥有指定的内核互斥,则 mutex_owned() 将返回非零值;如果调用线程当前不拥有指定的自适应互斥,则将返回零。

mutex_owner()

kthread_t *mutex_owner(kmutex_t *mutex)

mutex_owner() 返回所指定自适应内核互斥的当前属主的线程指针。如果当前不拥有指定的自适应互斥,或者指定的互斥为自旋互斥,则 mutex_owner() 将返回 NULL。请参见 mutex_owned(9F)

mutex_type_adaptive()

int mutex_type_adaptive(kmutex_t *mutex)

如果指定的内核互斥为 MUTEX_ADAPTIVE 类型,则 mutex_type_adaptive() 将返回非零值,如果不是该类型,则将返回零。如果互斥满足以下一个或多个条件,则它们是自适应的:

有关互斥的更多详细信息,请参见 mutex_init(9F)。Solaris 内核中的大多数互斥都是自适应的。

progenyof()

int progenyof(pid_t pid)

如果调用进程(与当前正在触发匹配探测器的线程关联的进程)是指定进程 ID 的子孙,则 progenyof() 将返回非零值。

rand()

int rand(void)

rand() 返回伪随机整数。返回的数字为脆弱的伪随机数,不应用于任何加密应用程序。

rw_iswriter()

int rw_iswriter(krwlock_t *rwlock)

如果指定的读取器-写入器锁定由写入者拥有或者写入者希望获取该锁,则 rw_iswriter() 将返回非零值。如果仅读取者拥有该锁,并且未阻止任何写入者;或者根本不拥有该锁,则 rw_iswriter() 将返回零。请参见 rw_init(9F)

rw_write_held()

int rw_write_held(krwlock_t *rwlock)

如果指定的读取器-写入器锁定当前由写入者拥有,则 rw_write_held() 将返回非零值。如果仅读取者拥有该锁,或者根本不拥有该锁,则 rw_write_held() 将返回零。请参见 rw_init(9F)

speculation()

int speculation(void)

speculation() 将推理跟踪缓冲区保留用于 speculate(),并返回此缓冲区的标识符。有关详细信息,请参见第 13 章

strjoin()

string strjoin(char *str1, char *str2)

strjoin() 创建一个字符串,该字符串包含 str1str2 的串连。返回的字符串分配到临时内存外部,因此它们仅在子句的持续时间中有效。如果没有可用的足够临时空间,则不会执行 strjoin,并且将生成错误。

strlen()

size_t strlen(string str)

strlen() 返回指定的字符串的长度(以字节为单位),不包括结尾的空字节。