27.11 DTrace
DTrace 源于 Sun Solaris,以 CDDL 许可证开源。FreeBSD 自 GENERIC 内核起内置支持,由内核模块实现。
警告
由于提交
commit/1ca7542a1b31fffefaac5a2ff45ef23f908b33c2的变更,本节内容仅适用于 16.0-CURRENT。
27.11.1 概述
DTrace 可用于诊断系统性能问题,调查和调试 FreeBSD 内核及用户空间程序中的意外行为,支持运行预编写的脚本,也可使用 D 语言编写自定义工具以满足特定分析需求。
FreeBSD 实现提供了对内核 DTrace 的完整支持,以及对用户空间 DTrace 的实验性支持。通过用户空间 DTrace 的 pid 提供者,可跟踪用户空间程序的函数边界,并在用户空间程序中插入静态探针以供后续跟踪。某些 Port,如 databases/postgresql18-server 和 lang/php85,提供了 DTrace 选项以启用静态探针。
Port databases/postgresql18-server 的 Makefile 部分源代码:
DTRACE_CONFIGURE_ENABLE=dtrace
DTRACE_LDFLAGS= -lelf
DTRACE_INSTALL_TARGET= installPort lang/php85 的 Makefile 部分源代码:
OPTIONS_DEFINE+=CGI CLI DEBUG DTRACE EMBED FPM IPV6 LINKTHR \
MYSQLND NOASLR PCRE PHPDBG ZTS JITDTrace 的官方指南由 Illumos 项目维护,网址为 illumos DTrace Guide。
27.11.2 实现上的差异
虽然 FreeBSD 中的 DTrace 与 Solaris 类似,但存在一些差异。主要区别是,FreeBSD 中的 DTrace 由一组内核模块实现。自 FreeBSD 10.0-RELEASE 起,运行 dtrace 时会自动加载所需模块。
FreeBSD 使用内核选项 DDB_CTF 以支持从内核模块及内核本身加载 CTF 数据。CTF 是 Solaris 的 Compact C Type Format,用于封装简化的调试信息,类似 DWARF 或传统的 stabs。CTF 数据由 ctfconvert 和 ctfmerge 构建工具添加到二进制文件中。ctfconvert 工具解析编译程序生成的 DWARF ELF 调试段,而 ctfmerge 将对象文件中的 CTF ELF 段合并到可执行文件或共享库中。
FreeBSD 提供的 provider 与 Solaris 略有不同。dtmalloc provider 较为特别,它能在 FreeBSD 内核中按类型跟踪 malloc。某些 Solaris 中的 provider 在 FreeBSD 中不存在。同时,两者均存在的 provider 可能不兼容,探针的参数类型不同。因此,在 Solaris 上编写的 D 脚本在 FreeBSD 上可能需要修改才能运行,反之亦然。
因安全机制差异,FreeBSD 上仅 root 用户可使用 DTrace。Solaris 中存在一些低级安全检查,而 FreeBSD 尚未实现。因此,/dev/dtrace/dtrace 设备文件严格限定为 root 用户可访问。
DTrace 本身以通用开发与分发许可证(CDDL)发布。支持 DTrace 的 FreeBSD 内核本身采用 BSD 许可,但 DTrace 内核模块以二进制形式分发或加载时适用 CDDL(参见源代码文件 dtrace_cddl.h)。
27.11.3 开启 DTrace 支持
DTrace 支持 已内置于 GENERIC 内核中。自 FreeBSD 10.0-RELEASE 起,运行 dtrace 时会自动加载所需模块。如需确保所有 DTrace 模块均已加载(某些脚本可能依赖未自动加载的 provider),可手动执行:
kldload dtraceall可安装 DTrace 工具包 sysutils/dtrace-toolkit。
使用 pkg 安装:
# pkg install dtrace-toolkit还可以使用 Ports 安装:
# cd /usr/ports/sysutils/dtrace-toolkit/
# make install clean该工具包内置了用于收集系统信息的现成脚本,包括检查打开的文件、内存、CPU 使用情况等。FreeBSD 基本系统中也内置了若干脚本,存放在 /usr/share/dtrace,在源代码中位于 share/dtrace/。
注意
/usr/share/dtrace 中的脚本是专门为 FreeBSD 移植的。DTrace 工具包中的脚本并非全部可直接在 FreeBSD 上运行,个别脚本需要一定修改才能使用。
DTrace 工具包使用 DTrace 特有的脚本语言 D 语言,与 C++ 相似。本节不对 D 语言进行深入讲解。如需概览,请参阅 FreeBSD 的 d 手册页。D 语言在 illumos 动态跟踪指南 中也有详细介绍。
如需将 DTrace 静态编译进内核,应在自定义内核配置文件中添加以下行,并重新编译内核:
options KDTRACE_HOOKS
options DDB_CTF
makeoptions DEBUG=-g
makeoptions WITH_CTF=1AMD64 架构的用户还应添加以下行:
options KDTRACE_FRAME此选项提供了对 dtrace_fbt 的支持。虽然没有该选项 DTrace 仍可工作,但函数边界跟踪功能将受限。
要为内核外模块添加 DTrace 支持,可在模块的 Makefile 中加入以下行:
CFLAGS+= -DKDTRACE_HOOKS此标志在编译过程中启用 DTrace 钩子,从而便于调试和监控模块。修改后,确保重新编译该模块以激活 DTrace 功能。
27.11.4 使用 DTrace
DTrace 脚本由一个或多个 探针(probes) 组成,每个探针都是一个可监控的点,并且与一个动作关联。每当探针的条件满足,关联的动作便会执行。例如,动作可能发生在文件打开、进程启动或代码行执行时。动作可以记录信息或修改上下文变量。上下文变量的读写允许探针共享信息,从而协作分析不同事件之间的关联。
要查看所有探针,需要以 root 身份执行以下命令:
# dtrace -l | more
ID PROVIDER MODULE FUNCTION NAME
1 dtrace BEGIN
2 dtrace END
3 dtrace ERROR
4 fbt kernel camstatusentrycomp entry
5 fbt kernel camstatusentrycomp return
6 fbt kernel cam_compat_handle_0x18 entry
7 fbt kernel cam_compat_handle_0x18 return
……以下省略……每个探针都有一个 ID、一个 PROVIDER(例如 dtrace 或 fbt)、一个 MODULE 和一个 FUNCTION NAME。
普通用户无法运行 DTrace:
$ dtrace -l
dtrace: failed to initialize dtrace: DTrace requires additional privileges示例:hotkernel 脚本用于识别消耗最多内核时间的函数。
# cd /usr/local/share/dtrace-toolkit
# ./hotkernel
Sampling... Hit Ctrl-C to end.按 Ctrl+C 停止执行。脚本结束后,会显示内核函数及其时间信息,并按时间升序排序:
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
^C
FUNCTION COUNT PCNT
kernel`vm_page_alloc_domain_iter 1 0.0%
kernel`cpu_idle 7 0.0%
kernel`em_update_stats_counters 8 0.0%
kernel`spinlock_exit 33 0.0%
kernel`acpi_cpu_c1 128203 100.0%该脚本也支持内核模块。使用 -m 选项运行:
# cd /usr/local/share/dtrace-toolkit
# ./hotkernel -m
Sampling... Hit Ctrl-C to end.
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
^C
MODULE COUNT PCNT
kernel 80153 100.0%示例:procsystime 脚本捕获并打印指定进程 ID(PID)或进程名称的系统调用时间。例如,启动一个新的 /bin/sh 实例后运行:
# ./procsystime -n sh
Tracing... Hit Ctrl-C to end...
^C
Elapsed Times for processes sh,
SYSCALL TIME (ns)
geteuid 4203
sigreturn 14858
__sysctl 61708
write 640999
read 2507609226如上所示,read 系统调用耗时最长,geteuid 耗时最短。
示例:基本系统内置的 hotopen 脚本会定期输出表格,列出哪些 UID 正在打开文件。
# cd /usr/share/dtrace
# ./hotopen
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
Files opened per UID in the last second.
2026 May 2 19:20:52
uid 0 count 2
2026 May 2 19:20:53
uid 0 count 2
2026 May 2 19:20:54
uid 0 count 1
^C
2026 May 2 19:20:55
uid 0 count 1借此可快速判断特定用户是否正在通过频繁调用 open/close 使系统进入“抖动”(thrashing)状态。