https://blogs.oracle.com/swan/entry/dtrace%E7%AE%80%E4%BB%8B
By samwan on 三月 20, 2007
记得几年前看过一部美国大片叫《全民公敌(Enemy of the State)》,在里面,谋杀国会议员的主谋强沃特和他的属下,为了取回记录着其犯罪事实的磁碟片,用高科技的卫星监视,使主人公史密斯的行踪处于严密的监控中。当时就对美国高科技跟踪系统惊叹不已。当然作为一个普通公民,是不希望自己受到监视的。但是对于计算机系统,如果能够对系统的运行情况进行监视并了如指掌,进而发现其中的臭虫(bug),那将是一件令IT管理者和开发者兴奋的事。今天我要介绍的SolarisTM Dtrace就是这样一个好帮手!
我的第一篇Blog就提到了Dtrace,但是没有作更多的说明。今天我将对Dtrace作比较详细的介绍,一是作为自己学习Dtrace的一点心得,二是希望对还没有使用Dtrace的朋友们提供一点入门知识,更详细的信息请参阅第一篇Blog中提到的资源。为了与中文版的《Solaris动态跟踪指南》保持一致,下面的术语都采用书中的翻译。
DTRACE(全称Dynamic Tracing)是SolarisTM 10中引入的一种可以对核心(kernel)和应用程序(user application)进行动态跟踪并且对系统运行不构成任何危险的技术。下面是理解Dtrace的几个要点:
1. Dtrace的实现是紧密地结合到核心里的(intimately integrated),即Dtrace的源代码是分布到了Kernel的各个部分中。除了Dtrace的执行程序dtrace.c和头文件<sys/dtrace.h>,<sys/dtrace_impl.h>外,其它实现dtrace的代码遍布到Solaris Source tree的各个文件。具体请参见 Bryan Cantrill的Blog - "The Observation Deck"
2. Dtrace架构中一个很重要的组件是"探测器(Probe)",简单讲,探测器就是核心源代码中某一个特点的”点“。在普通的Solaris 10内核中,这样的”点“有4万多个,而且还可以随着模块的加载而增加。探测器在没有被”启用(enable)“时,对核心是没有任何影响的,这时的核心与没有dtrace功能的核心如Solaris 8/9是没有任何区别的。当探测器被启用后,Solaris会动态地往核心中为启用的探测器加入相应的指令来实现探测器被"触发(fire)"时的“操作(action)"。
3. Dtrace架构可以简单的理解为”Dtrace提供器(Provider)和Dtrace使用者(Consumer)”模式。如下图所示:
”提供器“提供了”探测器“,而”使用者“通过libdtrace(3LIB)库和相应的设备文件或者其它方式来使用”提供器“提供的”探测器"。如上图所示,除了我们下面将会介绍的/usr/sbin/dtrace命令外,Solaris 10系统中还有很多收集统计信息的工具比如intrstat(1M),plockstat(1M),lockstat(1M)等都是Dtrace使用者。使用plockstat -V -p <pid>,你就可以看到plockstat使用的dtrace命令。
4. Dtrace本身是安全的,即不会对内核的运行造成影响。Dtrace可以读取内核变量,却不能修改内核变量。但是Dtrace提供了”破坏性(destructive)"的操作比如panic(),如果你使用了这些动作,是会中断系统运行的。
在学习Dtrace的过程中,要切记上面的几点。
下面就重点介绍一下Dtrace中日常使用最频繁的一个Dtrace使用者/usr/sbin/dtrace命令。dtrace(1M)可以以命令行形式调用,也可以通过D-script调用。D-script是用Dtrace提供的D语言来编写的脚本程序。D语言类似于C和awk,但是没有程序控制如for,if等机制,也许是为了更好的控制系统的稳定性。
命令行调用的例子: dtrace -n ‘syscall::open\*:entry{trace(execname)}‘
D-script例子:
#!/usr/sbin/dtrace -s syscall::open:entry, syscall::open64:entry { trace(execname); } |
不管是命令行方式还是脚本方式,都要指定至少一个探测器。每个探测器都是一个“四元组(4-tuple)",但是有的部分可以省略。探测器的具体格式如下:
Provider:Module:Function:Name
各部分的含义如下:
- Provider即提供器,发布此探测器的Dtrace提供器的名称。比如:syscall是所有系统调用的提供器,sysinfo是系统统计信息的提供器,proc是进程信息的提供器。不同系统不同版本的Solaris的提供器的数量不同。使用下面的命令可以查看系统中有多少个提供器.
#dtrace -l|grep -v "PROVIDER"|awk ‘{print $2}‘|sort -u
- Module即模块,是此探测器对应于特定的程序位置时,其所在模块的名称。对于应用程序,模块名可以是动态链接库的名字,比如:libc,或者主程序a.out。有的探测器没有模块名。
- Function即函数,探测器所在函数的名称
- Name即名字,最后一个组成部分。
探测器的四元组名字如果某个部分为空,则表示匹配该字段的所有可能性,星号(\*)也是通配符,表示匹配任意字符串。现在我们再来看上面的两个例子。第一个命令行例子表示启用syscall提供器中所有模块里面名字以open开头的函数的entry探测器;而第二个脚本例子表示匹配syscall提供器中所有模块里面名字是open或者open64函数的entry探测器,其中的逗号表示或者的关系。命令行方式调用时,如果不使用-l开关,则指定的探测器将被启用,对于脚本方式,-s后面即D-script程序的正文部分。
一个D程序的结构如下:
0 #!/usr/sbin/dtrace -s
1 pragma D option quiet
2 probe_description_1
3 / predicate_1 /
4 {
5 action_1;
6 action_2;
7 ...
8 action_n;
9 }
10 probe_description_2
11 / predicate_2 /
12 {
13 action_1;
14 action_2;
15 ...
16 action_n;
17 }
...
18 probe_description_n
19 / predicate_n /
20 {
21 action_1;
22 action_2;
23 ...
24 action_n;
25 }
上面的伪代码(pseudo-code)描述了一个D程序的大致结构,其中除了探测器描述部分,其它的部分如谓词、操作都不是必须的。第0行指明D程序的解释器(interpreter),就是/usr/sbin/dtrace;第1行使用pragma关键字指定特定的D程序编译指令;从第2行起就是对相应的探测器的启用,并定义在指定的探测器被触发时应该执行的操作,操作以分号结尾。其中,在探测器描述和操作之间用 / / 符号隔开的部分称为"谓词(Predicate)"。前面已经提到,在D语言中,没有if语句和循环,只有通过谓词来进行判断,谓词是一系列的逻辑运算,如果计算结果是false(0),则忽略探测器的触发,当然更不会执行该探测器定义的任何操作;只有当谓词计算为true(非0)时,相应的操作才会被执行。D程序的执行是从上至下顺序执行的,花括号{}包围的部分是对应探测器被触发且谓词为真时的执行子句块,对于同一个探测器描述,可以指定多个执行子句块。
当你编辑完成一个D程序,并且使用dtrace -s或者通过直接添加执行权限来执行时,Dtrace首先会将你的脚本程序编译成一个安全的中间格式(有点类似于Java程序的运行机制),然后才会被加载到内核中执行。Dtrace的执行环境还会检查并处理运行时错误(run-time errors)比如被零除(dividing by zero),访问无效地址等。因此Dtrace是相当安全的。
当Dtrace程序被加载到内核执行时,相应的探测点被启用,如果有涉及探测点的事件发生,我们就把它称之为“触发”,如果此时谓词计算为true,则相应的操作就会被执行。为便于大家理解“启用”和“触发”两个概念,我们举一个日常生活中的实际例子。
现在全国各个城市为了更好地规范交通秩序,都安装了很多“电子警察”(就是“探测器”),安装完成就打开(即“启用”),如果有车闯红灯,就会激活安装在地上的感应线(”触发“),那么”电子警察“就会拍照,很快罚单就会送到你家里(这就是”操作“)。
通过上面这个例子,大家应该有个更加形象的认识了吧。
作为今天的结束,下面是一个监视谁(用户ID)使用什么命令访问一个文件(文件以参数形式传递)的例子。
who_access_thisfile.d
#!/usr/sbin/dtrace -qs
syscall::creat\*:return,
syscall::open\*:return
/arg0 != -1 && fds[arg0].fi_pathname == $1 /
{
printf("uid#%d %s %s\\n",uid,execname,$1);
}
chmod +x who_access_thisfile.d,然后执行./who_access_thisfile.d /etc/passwd,在另一个终端上试试cat /etc/passwd, vi /etc/passwd,看看你都看到了什么信息,你原来能做到吗?
更多的信息,将在下一次中介绍。