DTrace

这两天搞了一把DTrace,貌似还挺有意思的。

DTrace是个动态跟踪工具,可以在动态环境下向内核/程序中插入探头(probe),收集处理以及显示一些信息。

其实本来搞DTrace是为了一个FreeBSD的bug。搞chromecast的时候发现,IP multicast的目标mac不对。不过后来分析代码大致定位到了出问题的文件,然后在最新代码里发现这个bug已经修了(PR 185395),于是纯粹就是学习DTrace了。

一开始在我的机子上DTrace还跑不起来,后来发现是因为我跑的UEFI分支里的内核路径有些特别,于是DTrace找不到符号。打了个补丁(dt_kernel_path.patch)之后,dtrace终于能用了。

DTrace脚本的主体是一堆事件处理函数。跑DTrace脚本的时候,系统碰到某个符合的事件,就调用对应的函数。这些函数用D语言(不是那个D语言,是DTrace自己的一种语言)写成,语法和C差不多。基本的赋值/比较都能干,不过没有循环,也没有if/then/else。能控制流程的,基本上就是每个事件自带的条件,和三元操作符,也就是?:。

虽然前面那个bug是修了,我还是搞了个简单脚本来试DTrace。我希望观察arpresolve函数的参数和返回结果,也就是那个返回IP对应mac地址的函数。

arpresolve的原型是

int
arpresolve(struct ifnet *ifp, struct rtentry *rt0, struct mbuf *m,
const struct sockaddr *dst, u_char *desten, struct llentry **lle)

我希望查看dst里的ip地址,和desten里返回的mac地址。

DTrace脚本如下:

fbt::arpresolve:entry
/execname == "ping" && arg4 != 0/
{
ip = ((struct sockaddr_in *)arg3)->sin_addr.s_addr;
a = (ip & 0xff000000) >> 24;
b = (ip & 0xff0000) >> 16;
c = (ip & 0xff00) >> 8;
d = ip & 0xff;
a4 = (unsigned char *)arg4;
printf("%s %d.%d.%d.%d", execname, d, c, b, a);
}
 
fbt::arpresolve:return
/execname == "ping" && a4 != 0/
{
printf("%s %02x.%02x.%02x.%02x.%02x.%02x", execname, a4[0], a4[1], a4[2], a4[3], a4[4], a4[5]);
}

这里定义了俩probe,一个在arpresolve的入口(entry),还有一个在返回的时候触发。probe的名字有四个部分:

<provider>:<module>:<function>:<name>

provider就是提供这个probe的组件,例如fbt就是Function Boundary Tracing,其他还有syscall追踪system call的调用等。fbt可以追踪几乎内核里所有函数的调用……

module和function确定了包含这个probe的函数。module是模块名,function是函数名。如果一个部分为空(例如这里的module),那相当于*。

name就是这个特定probe的名字,这里entry和return都是。

第一个probe其实完整名字是fbt:kernel:arpresolve:entry,最简单弄成::arpresolve:entry也可以,反正没有别的。

每个probe还能限制触发条件,例如这里的execname == “ping” && arg4 != 0。这个稍微可以弥补一些没有if的缺陷。

在fbt的entry probe中,arg0, arg1…可以用来访问参数,而在return那里,arg0有返回地址(用以区分是哪个return返回的),arg1有返回值。

据说args[i]可以访问带类型的参数,但是我没试成…… 所以这里弄来arg3, arg4强转成了参数类型。DTrace会读取内核里的类型信息,所以各个数据类型直接就能用,这里就不用自己定义sockaddr_in了。转完之后,直接打出来就可以了。

因为在return那边没法读取参数,在entry里我先存了起来。

存了脚本,用dtrace -s就可以跑了。跑出来大概是这样:

> sudo dtrace -s arp.d
dtrace: script ‘arp.d’ matched 2 probes
CPU     ID                    FUNCTION:NAME
  1  20699                 arpresolve:entry ping 239.255.255.250
  1  20700                arpresolve:return ping 01.00.5e.7f.ff.fa
  3  20699                 arpresolve:entry ping 239.255.255.250
  3  20700                arpresolve:return ping 01.00.5e.7f.ff.fa
  1  20699                 arpresolve:entry ping 239.255.255.250
  1  20700                arpresolve:return ping 01.00.5e.7f.ff.fa
^C

当然这个是已经修完了的,修之前ip是网关的ip,mac变成了网关IP根据multicast对应规则转换成mac的结果,例如ip是192.168.1.1,mac是01.00.5e.28.01.01,总之很诡异…… 其实multicast情况下mac应该是用dest ip转的,那个修正也就是修了这个。

DTrace看上去挺有用的,对调试来说简直是静态调试的理想环境,插入调试语句都不用中断运行,也不用担心调试语句写错导致系统crash,还可以访问符号,大大加快调试速度。profiling应该也挺有用,不过我还没研究。

不过没有循环我倒是觉得可能会造成一些麻烦,例如在链表里找东西之类就不好找了。估计还是为了安全性验证和效率考虑吧,不过如果信得过的话,允许程序员用也未尝不可啊。

DTrace一开始是Solaris搞的,后来Mac和FreeBSD都port了一份过来。根据布总的教导,各个系统都有,windows下面有个可以处理SQL语句的,Linux虽然官方内核没有,但是有一堆第三方的。