SystemTap是一个Linux调试和性能分析工具,可用于应用层和内核层的分析,但主要侧重内核层。SystemTab可以在不修改内核代码、不重复编译内核、不重启机器的情况下,收集运行内核的信息并使信息可视化。调试人员可以利用它绘制函数调用关系图,打印寄存器信息和调用栈,输出内核中指定变量(可以是局部变量)。它如同一个更加方便prink,方便调试人员观察内核行为,诊断错误异常,分析系统性能。在YAVIS开发过程中,我们使用SystemTap分析包发送和接收情况,并分析通信性能瓶颈。
SystemTap工作流程
SystemTap使用了Kprobe技术探测内核信息,辅以Relayfs向用户传递消息。
SystemTap首先将SystemTap脚本文件翻译成C源文件。这个C源文件实际上是一个内核模块,实现了脚本文件中描述的功能。接着SystemTap编译源文件获得二进制模块文件,并动态加载模块。模块被载入运行内核后,会报告脚本文件中指定的一些事件。事件会触发脚本文件中编写的处理函数,执行相关操作。一般操作内容是:收集所需信息,并通过标准输出打印给用户。SystemTap会话结束于用户发出中断信号,即Ctrl + C,内核模块将随之被安全卸载。
SystemTap提供了一些内置函数,帮助我们快速开发测试脚本。常用的内置函数如:
print(str) - 打印str的值
printf(fmt) - 如同C语言的printf函数
probefunc() - 返回当前探测函数的函数名
execname() - 返回当前进程的名字
pid() - 返回当前进程ID
uid() - 返回当前进程用户ID
cpu() - 返回运行当前进程的CPU号
另外一些内置功能以Tapset的形式出现。Tapset相当于SystemTap的库。它提供的功能不仅仅是函数,还包括一些预定义的探测点,如:
timer.ms(N) - 每N毫秒探测一次(用于性能测试)
begin - 探测模块加载时执行一次
当然,用户也可以开发自己的Tapset。
SystemTap的使用
在CentOS中SystemTap可以用下面命令安装升级:
sudo yum installsystemtapsystemtap-runtime
编写脚本后,使用这个命令执行:
stap
注意:如果探测的是模块,要确保模块被复制到/lib/modules//中,否则脚本解析时会在module处出错。
SystemTap脚本语言
SystemTap语言是一种与C语言和awk语言类似的脚本语言。限于篇幅,这里并不系统地介绍SystemTap语言,而是结合毕业设计的调试场景,使用例子说明SystemTap的语法特征和编程结构。
#!/usr/bin/stap
probe module("yavis").function("*").call {
printf("%s -> %s\n", thread_indent(1), probefunc())
}
probe module("yavis").function("*").return {
printf("%s -> %s\n", thread_indent(-1), probefunc());
}
上述代码的功能是输出YAVIS的代码调用关系图。第一行描述脚本采用的解释器是stap程序。第二行表示在yavis模块中所有函数中插入探针,并在这些函数调用时触发第三行的代码。第三行代码向标准输出打印一串信息,信息包括当前函数的名字,由内置的probefunc收集。第五至第七行代码与上面的类似,只是在函数返回时触发。这样,所有YAVIS模块的函数在调用时输出函数名,返回时再次输出函数名,同时由内置的thread_indent函数负责自动的缩进,最终绘制了整个YAVIS模块的函数调用关系图。
#!/usr/bin/stap
probe module("yavis").function("yavis_poll") {
if ($revt->type == 0) {
printf("-- package received --\n")
printf("revt.msg_len = %d\n", $revt->msg_len)
for (i = 0; i < $revt->msg_len; i++) {
printf("%x", $revt->rbuff[i])
}
printf("\n")
} else if ($sevt->type == 0) {
printf("-- package sent --\n");
}
}
probe module("yavis").function("yavis_tx") {
printf("-- sending package --\n");
printf("skb->len = %d\n", $skb->len)
for (j = 0; j < $skb->len; j++) {
printf("%x", $skb->data[j])
}
printf("\n")
}
上面这段脚本可以让我们在发送和接收过程中查看数据。注意结构体类型的数据,无论指针与否,一律使用->方式访问成员变量。
附注
systemtap可由包管理器直接安装。但是想探测较新版本的内核,建议手动编译git仓库中的最新版本systemtap。当然由于systemtap开发相对内核开发有些许置后,所以最新的systemtap一般没法探测最新版本的内核。具体能不能,留意stap --version命令的输出。
参考
[1] SystemTap: Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems. IBM, 2009.