Solaris内置的Dynamic Tracing (DTrace) 功能提供一个动态的跟踪环境。
DTrace is a comprehensive dynamic tracing framework for the Solaris Operating System. DTrace provides a powerful infrastructure that permits administrators, developers, and service personnel to concisely answer arbitrary questions about the behavior of the operating system and user programs.
与 truss 和相似的工具不同,可以使用 DTrace 检查正在运行的程序的内部情况,而不只是查看系统调用。DTrace 可以显示应用程序的函数名、它所依赖的任何库以及调用的内核函数。这把跟踪的方便性提高到与调试器差不多的水平。但是,与调试器不同,不能修改值,也不能暂停或以其他方式改变应用程序的执行过程。只能跟踪执行过程,而不能控制它。
DTrace 的另一个独特之处是可以编写跟踪脚本,称为“D”的脚本语言,使用D语言,很容易编写动态启动probe、收集信息及处理信息的脚本。D脚本使用户可以方便地同其他人员共享知识和故障检修方法。Solaris 10中包含大量有用的D脚本,在Sun公司的BigAdmin 站点:sun.com/bigadmin/content/dtrace/和OpenSolaris项目站点:opensolaris.org/os/community/ dtrace/上可以找到更多脚本。
DTrace 会在应用程序中添加检测机制,从而识别不同的执行点。这些执行点称为探测(probe),包括在内核、库和程序中定义的探测。内核、库和用户程序中的所有函数都可以指定为探测。另外,可以使用静态定义的探测识别感兴趣的特殊执行点。例如,在内核中可以使用探测识别向磁盘写数据的执行点。开发人员可以在程序中添加特定的探测,从而允许用户启用跟踪。这些探测称为 User-land Statically Defined Tracing(USDT)。
Probe就像遍布于Solaris系统中各个感兴趣位置的可编程传感器。
可以使用 dtrace 工具获得探测列表;-l 命令行选项列出系统中定义的所有探测:$ dtrace -l。例如在我用的solaris上,有超过5万个probe点。
-bash-3.00# dtrace -l | wc -l
51927
Probe
Probe的定义如下:
Description
/Predicate/
{
Action
}
Description使用四个字段:provider:module:function:name。
provider: 指定要使用的实现方法。例如,syscall provider用以监控系统调用,而io provider用以监督磁盘IO。
module: 模块。
function: 函数,可以使用*和?通配符。
name: 通常代表函数中的位置。
例如:
1) 监视系统调用
dtrace -n 'syscall:::entry' -o trace.log
*输入ctrl+c中止。
*-o file选项把trace结果输出到文件而不是stdout。
2) 监视系统调用open
dtrace -n 'syscall::open:entry' -o trace.log
*输入ctrl+c中止。
3) 监视函数输入参数
监视程序mytest.exe中函数add的输入参数
*-c指定command,可以使用-p pid来attach到一个进程.
-bash-3.00$ more mytest.cc #include <iostream> int add(int i1, int i2) { return (i1 + i2); } int main(int argc, char** argv) { printf("1+2=%d\n", add(1,2)); return 0; } -bash-3.00$ CC mytest.cc -o mytest.exe -bash-3.00$ -bash-3.00# dtrace -n 'pid$target:mytest.exe:*add*:entry /execname=="mytest.exe"/ { printf("%d %d", arg0, arg1) }' -o trace.log -c './mytest.exe' dtrace: description 'pid$target:mytest.exe:*add*:entry ' matched 1 probe 1+2=3 dtrace: pid 19991 has exited -bash-3.00# more trace.log CPU ID FUNCTION:NAME 0 51942 __1cDadd6Fii_i_:entry 1 2
4)编写脚本
-bash-3.00# more mytest.trace.sh #!/usr/sbin/dtrace -s #pragma D option quiet pid$target:mytest.exe:*add*:entry { printf("Add(%d,%d)",arg0,arg1); } -bash-3.00# -bash-3.00# ./mytest.trace.sh -o trace.log -c ./mytest.exe 1+2=3 -bash-3.00# more trace.log Add(1,2) -bash-3.00#
5) 聚合函数
聚合(Aggregation)具有如下构造函数:
@table_name[index1[,index2...]] =aggregate_function()
例如:
@table[probefunc]=count() ;
@table[probemod,probefunc]=count();
probemod, probefunc是模块名,函数名的内置变量。
聚合函数count()追踪函数被调用的次数。
其他常用的聚合函数有min、max、average、sum等。
*-q选项只输出D脚本中的trace(),printf()函数等的输出,不会为每个probe输出一行跟踪结果。
-bash-3.00# dtrace -n 'pid$target::strcpy:entry { @table[probemod,probefunc]=count(); }' -q -c './mytest.exe' 1+2=3 libc.so.1 strcpy 1 LM1`ld.so.1 strcpy 4
6) 记录执行时间
*为了让结果更明显,修改mytest.cc在add()函数中加上sleep(1),重新编译mytest.exe;
*可以使用self变量来保存记录。向self变量添加的任何数据都被设置为线程局部数据,适用于多线程程序。
*如果是attach到一个进程进行监控,有可能entry probe在attach之前就已经执行,在return probe加上的predicate可以处理这种情况。
-bash-3.00$ more mytest.cc #include <iostream> #include <unistd.h>int add(int i1, int i2) { sleep(1); return (i1 + i2); }
int main(int argc, char** argv) { for (int i=0; i<6; ++i) { printf("1+2=%d\n", add(1,2)); } return 0; } -bash-3.00$ CC mytest.cc -o mytest.exe -bash-3.00$
-bash-3.00# more mytest.trace.sh #!/usr/sbin/dtrace -s #pragma D option quiet pid$target:mytest.exe:*add*:entry { self->start[probefunc]=timestamp; } pid$target::printf:entry { self->start[probefunc]=timestamp; } pid$target:::return /self->start[probefunc]!=0/ { @elapsed[probefunc]=sum((timestamp-self->start[probefunc])/1000000); self->start[probefunc]=0; } -bash-3.00# ./mytest.trace.sh -q -c ./mytest.exe 1+2=3 1+2=3 1+2=3 1+2=3 1+2=3 1+2=3 printf 1 __1cDadd6Fii_i_ 6000 -bash-3.00#
7) C++程序
C++程序编译以后生成的符号名字和代码中是不一样的。不过可以使用nm命令找到对应关系。例如:
-bash-3.00$ more math.h class Math { public: int add(int i1, int i2); }; -bash-3.00$ more math.cc #include "math.h" int Math::add(int i1, int i2) { return (i1 + i2); } -bash-3.00$ more mytest.cc #include <iostream> #include "math.h" int main(int argc, char** argv) { Math m; printf("1+2=%d\n", m.add(1,2)); return 0; } -bash-3.00$ CC mytest.cc math.cc -o mytest.exe mytest.cc: math.cc: -bash-3.00$ /usr/ccs/bin/nm -C mytest.exe | grep Math [59] | 69296| 40|FUNC |GLOB |0 |7 |int Math::add(int,int) [__1cEMathDadd6Mii_i_] -bash-3.00$
这样就找到了Math::add()对应的名字是__1cEMathDadd6Mii_i_,现在可以使用这个名字来监控,
-bash-3.00# dtrace -n 'pid$target::__1cEMathDadd6Mii_i_:entry { @table[probemod,probefunc]=count(); }' -q -c './mytest.exe' 1+2=3 mytest.exe __1cEMathDadd6Mii_i_ 1 -bash-3.00#
9)一些例子
dtrace -n 'proc:::exec-success {printf("cmd=%s,pid=%d,uid=%d\n",execname,pid,uid);}'
监视系统中成功启动的进程名字,pid,uid
dtrace -q -n 'proc:::signal-send /args[2]==SIGKILL/ {printf("%d sent to %s uid=%d\n",args[2],args[1]->pr_fname,uid);}'
监视接收到SIGKILL的进程。(pr_fname是接收进程的psinfo_t结构的成员)