1. 常用技巧
systemtap可以实现交叉编译:
编译可执行模块如下:
stap -r kernel_version script -m module_name
运行命令如下:
staprun module_name.ko
stap命令会读取脚本的指令,并翻译成C代码,编译成内核模块加载到内核。
Staprun命令运行指令,并不会去翻译或编译。
使用-v参数可以输出会话很多信息。使用-vvv可以输出更加详细信息。
其他常用的参数有如下:
-o可以将输出指定到文件
-S可以指定日志大小
-x可以绑定到某个进程,这个比较好用。
-e使用脚本而不是文件
-d可以指定模块,例如文件系统模块,这样就可以输出文件系统相关函数,也可以使用--all-modules,加载所需要的映射信息。
-F使用flight recoreder模式,后台运行,只记录最进输出。记录方式可以是内存和文件方式。内存默认是1MB,可以通过-s选项来设置。例如-s2表示设置2MB。如果是文件通过-S来设置,第一个参数是大小,第二该参数是最近保存的文件数量,老的文件会被删除。
例如:
stap -F -o /tmp/pfaults.log -S 1,2 pfaults.stp
2. 事件
systemtap中事件分为同步的和异步的。
同步事件包括:
syscall.system_call 系统调用
vfs.file_operation 虚拟文件系统的操作
kernel.function(“function”) 内核函数
kernel.trace(“tracepoint”) 静态探针tracepoint
module(“module”).function(“function”) 模块中函数
异步事件是没有绑定到代码中的指令,有计数器,定时器等,包括如下:
begin 会话开始
end 会话结束
timer 定时器事件
关于支持的事件可以使用命令man stapprobes。
3. 函数
tid()当前线程id
uid()当前用户id
cpu()当前cpu号
gettimeofday_s()从1970开始的秒
ctime() 转换时间从秒到日期
pp()描述当前探针的字符串
thread_indent()当一个探针的处理没结束又触发下一个探针的时候,可以进行缩进打印,非常便于观察。
name 只能用于系统调用
target()当使用-x参数绑定进程的时候,可使用来判断pid()==target(),从而过滤其他进程触发的事件。
4. 变量
通常变量是局部的,如果要使用全局的变量,需要使用global。
目标可用变量可以通过-L参数获取,例如:stap -L ‘kernel.function(“vfs_read”)’
出现如下,表示vfs_read函数的可以用变量或结构。
kernel.function("vfs_read@fs/read_write.c:448") $file:struct file* $buf:char* $count:size_t $pos:loff_t*
如果变量定义在其他文件中,可以通过如下方式来引用
@var(“varname@src/file.c”)
如果是数据结构,可以通过->来获取其中的域值,例如
@var(“files_stat@fs/file_table.c”)->max_files
获取指定地址的字节:kernel_char(address)
kernel_short(address)
kernel_int(address)
kernel_long(address)
kernel_string(address)
kernel_string_n(address,n)
为了方便获取参数,systemtap提供了一个方法
直接使用$$var就可以打印函数参数和变量。
如果使用$$locals,就是打印局部变量
$$parms就只打印函数阐述
$$return 打印返回值,只在返回探针能用。
例如:
#stap -e 'probe kernel.function("vfs_read") {printf("%s\n",$$parms);exit();}'
file=0xffff880006761e00 buf=0x7fff4bb16e70 count=0x2004 pos=0xffff8800004cff48
这个得到的如file本来就是一个数据结构,意义并不是很大,如果能得到该数据结构中的域,那将更好。可以通过在变量后面增加一个$来实现。
#stap -e 'probe kernel.function("vfs_read") {printf("%s\n",$$parms$);exit();}'
如果域中还有指针,要继续解锁,那么就增加一个$来实现,如:
#stap -e 'probe kernel.function("vfs_read") {printf("%s\n",$$parms$$);exit();}'
有些结构类型可能不能确定,那么可以使用@cast来进行类型转化。
另外可以使用@define来确定是否有可用的变量
5. 参数
脚本支持参数输入的,类似shell脚本,不过在脚本中通过$和@两个来获取参数,其中$表示整数,@表示字符串。
6. 组合数组
例如:foo[“tom”]=23
foo[“dick”]=24
其实就是python中的字典,一个key对应一个value。
不过key可以是多个,最多可以是9个,相互用逗号隔开。
组合数组必须是global的。
也可以如下,将线程ID和时间关联起来。
foo[tid()] = gettimeofday_s()
l  访问的时候可以直接通过foo[tid()]来进行访问,甚至能实现++计算。
如果要遍历数组中的元素,可以使用foreach ( a in foo),这个语法是python简直了。
甚至可以按升序或降序处理如下:
foreach (a in foo- limit 10),其中-表示降序,升序可以用+,limit 10表示前10个。
l  删除可以使用delete命令。
l  判断某个key是否在组合数组中,可以使用[“a”] in foo来判断。
l  还可以进行累加例如foo[“a”]<<<$count,使用<<<来实现。
l  组合数据还支持聚合操作,@extractor(variable/array index expression).extractor.其中extractor可以是count,sum,min,max,avg。例如@count(reads[execname()]),@sum(reads[execname()].
7. tapsets
tapsets是预先写好的探针和函数库,当用户运行systemtap脚本时,systemtap会检测这个库,然后再去翻译成C。
这个库位于/usr/share/systemtap/tapset目录中,tapsets是不能直接执行的,它是一个抽象层让用户更加方便的来定义事件和函数。例如thread_indent(),execname()等。
8. 嵌入C代码
可以通过%{和%}来嵌入C代码。嵌入C代码后,运行可以使用stap -g script,以专家模式运行。