Solaris上使用DTrace进行动态跟踪

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结构的成员)

你可能感兴趣的:(Solaris上使用DTrace进行动态跟踪)