当我们利用systemtap跟踪内核时,首先需要了解systemtap提供了什么跟踪点,这些跟踪点在systemtap中被称为probe事件。systemtap的语法类似于awk和bpftrace语法,是一种事件驱动的语言。当内核执行到时某一种事件被触发就会处理相应的动作。格式如下:
probe probe-point { statement }
systemtap中支持的事件分为很多类型,本文将触发介绍一些最常用的probe类型。查看probe点的命令为:
stap -l *.* #列出对应格式的事件
stap -L *.* #列出对应格式的事件,并且包括支持的详细参数列表
stap -L syscall.*
stap -L 'kernel.trace("*")'
stap -L 'kernel.function("*")'
该事件标志着脚本执行的开始。举例:
#!/usr/bin/env stap
probe begin
{
printf("hello world\n");
}
这个脚本会在运行时打印hello world
该事件标志着脚本执行的结束,举例:
#!/usr/bin/env stap
probe begin
{
printf("helle,world\n");
}
probe end
{
printf("Goodbye,world\n");
}
这个脚本会在开始时打印"helle,world",结束时打印"Goodbye,world"。
系统调用事件,system_call是对应的系统调用名字,如果要看系统中支持所有系统调用事件,可以如下命令:
stap -L syscall.*
比如在脚本中可以这样使用:
probe syscall.openat
{
if(pid() != stp_pid())
printf("PID: %d\tNAME: %s\tARGSTR: %s\n",pid(), name, argstr);
}
如果要跟踪一个系统调用返回,那么只需要在后面加上return后缀,例如:syscall.openat.return
这个类型用于跟踪内核中tracepoint事件,通过如下命令查看systemtap支持的tracepoint:
stap -L 'kernel.trace("*")'
举例跟踪sched:sched_switch事件,查看-L列出的信息:
kernel.trace("sched:sched_switch") $preempt:bool $prev:struct task_struct* $next:struct task_struct*
通过列表出的信息可以看到后面能够引用的变量有$preempt、 $prev和 $next::
probe kernel.trace("sched:sched_switch")
{
printf("prev pid: %d next pid:%d\n", $prev->pid, $next->pid);
}
通过如下命令查看systemtap支持的vfs事件:
stap -l vfs.*
在我的机器上,输出如下所示:
vfs.__add_to_page_cache
vfs.__set_page_dirty_buffers
vfs.add_to_page_cache
vfs.buffer_migrate_page
vfs.do_mpage_readpage
vfs.do_sync_read
vfs.do_sync_write
vfs.open
vfs.read
vfs.readv
vfs.remove_from_page_cache
vfs.write
vfs.writev
这个类型用于跟踪内核函数,查看支持的所有函数:
stap -L 'kernel.function("*")'
如果要想跟踪一个函数的kretprobe点,只需要在后面加上 return后缀,例如:kernel.function(“function”).return
和上面的类似,不过这个功能追踪的函数只限定于特定的module中。比如:
probe module("ext3").function("*") { }
probe module("ext3").function("*").return { }
timer定时器类型,常用的具体事件:
timer.hz(hertz)
timer.jiffies(jiffies)
timer.s(seconds)
timer.ms(milliseconds)
timer.us(microseconds)
timer.ns(nanoseconds)
timer.ms(200).randomize(50) # 每隔 200 毫秒触发一次,带有线性分布的随机附加时间(-50 到 +50)
这里罗列一些probe点,便于后续快速查询:
begin // 在脚本开始时触发
end // 在脚本结束时触发
kernel.syscall.* // 执行系统调用时触发
kernel.trace(PATTERN) //执行到内核tracepoin时触发
kernel.function(PATTERN) // 在函数执行时触发
kernel.function(PATTERN).call //在函数执行时触发
kernel.function(PATTERN).return //在函数返回时触发
kernel.function(PATTERN).inline //inline函数处执行
kernel.function(PATTERN).label(LPATTERN) //执行到label时触发
module(MPATTERN).function(PATTERN) //执行到特定module特定函数时触发
module(MPATTERN).function(PATTERN).call //执行到特定module特定函数时触发
module(MPATTERN).function(PATTERN).return //执行到特定module特定函数返回时触发
kernel.statement(*@kernel/time.c:296) // 探测到确切的代码行
module("virtio_net").statement("receive_buf@drivers/net/virtio_net.c:1346") // 探测到确切的代码行