linux|linux 实用工具----systemtap

简介 ?SystemTap是一个诊断Linux系统性能或功能问题的开源软件。它使得对运行时的Linux系统进行诊断调式变得更容易、更简单。有了它,开发者或调试人员不再需要重编译、安装新内核、重启动等烦人的步骤。
?我们一般写程序,都会加入相应级别的日志,可以帮助我们定位问题或者观察某些代码路经而不用去使用gdb。但是系统编程,就不能狂打日志(经实验在io路经加日志,rsyslog会经常挂,而且/var/log/meessages里面的信息过多时查找别的心痛错误非常费劲),而且很多调用栈都处于 kernel space,那么普通的调试手段就显得捉襟见肘了。
?此时 systemtap 就能派上用场,他会在内核函数加 probe 探针,对 kernel space 函数调用进行统计汇总,甚至可以对其进行干预。但是对 user space 调试支持不是很好。
环境配置

  1. ?我的实验环境是Centos7,内核版本kernel-3.10.0-514.26.2.el7.x86_64,根据版本到官网去下载如下对应的包:
  • vault.centos.org
  • debuginf.centos.org
[root@xt2 ~]# rpm -qa |grep kernel
kernel-headers-3.10.0-514.16.1.el7.x86_64
kernel-debuginfo-common-x86_64-3.10.0-514.26.2.el7.x86_64
kernel-3.10.0-514.26.2.el7.x86_64
kernel-debuginfo-3.10.0-514.26.2.el7.x86_64
kernel-devel-3.10.0-514.26.2.el7.x86_64
装底下这些kernel包的时候有可能要替换一些已有的包、
kmod-20-9.el7.x86_64.rpm
kmod-libs-20-9.el7.x86_64.rpm
linux-firmware-20160830-49.git7534e19.el7.noarch.rpm
xfsdump-3.1.4-1.el7.x86_64.rpm
xfsprogs-4.5.0-8.el7.x86_64.rpm
把本地老的rpm -e --nodeps xxxx 删掉然后装上面新的
  1. ?安装工具
    yum install systemtap
开始编辑stap脚本 【linux|linux 实用工具----systemtap】一个简单的例子如下:
#!/usr/bin/env stapglobal fuck = 0 global g_ino = 0probe begin { printf("probe begin\n") }probe module("fuse").function("fuse_finish_open") { inode = pointer_arg(1) ino = @cast(inode, "struct inode")->i_ino g_ino = ino printf("coming fuse_finish_open ino: %lu\n", ino) }probe module("fuse").function("fuse_file_mmap") { printf("coming fuse_file_mmap,,,,\n") }probe module("fuse").function("fuse_link_write_file") { printf("coming fuse_link_write_file\n") #print_backtrace() }probe kernel.function("generic_file_aio_write"){ fuck = 1 nr_segs = ulong_arg(3) pos = ulong_arg(4) printf("coming generic_file_aio_write nr_segs: %lu %lu\n", nr_segs, pos) }probe kernel.function("page_cache_tree_insert"){ino = $mapping->host->i_ino if (ino == g_ino) { index = $page->index nrpages = $mapping->nrpages printf("coming page_cache_tree_insert ino: %lu index:%lu pages: %lu\n", ino, index, nrpages) } } probe kernel.function("page_cache_tree_delete"){ino = $mapping->host->i_ino if (ino == g_ino) { index = $page->index nrpages = $mapping->nrpages printf("coming page_cache_tree_delete ino: %lu index:%lu pages: %lu\n", ino, index, nrpages) #print_backtrace() } } probe kernel.function("__set_page_dirty_nobuffers"){ page = pointer_arg(1) index = @cast(page, "struct page")->indexprintf("coming set_page_dirty_nobuffers: %lu \n", index) #print_backtrace() printf("---------------------------------------- page dirty\n") }probe module("fuse").function("fuse_release"){ fuck = 1 printf("coming fuse_release****************** \n") print_backtrace() }#probe kernel.function("tag_pages_for_writeback") #{ #if (fuck == 1) { #index = ulong_arg(2) #end = ulong_arg(3)#printf("coming tag_pages_for_writeback: index %luend: %lu\n", index, end) #} #}#probe kernel.function("pagevec_lookup_tag"){ #if (fuck == 1) { #tag = int_arg(4) #printf("pagevec_lookup_tag %d\n", tag) #} #} #probe kernel.function("pagevec_lookup_tag").return ? #{ #printf("pagevec_lookup_tag return %u\n", $return) #print_backtrace() #}#probe kernel.function("pagevec_lookup_tag").return{ #printf("find_get_page return %u\n", $$return) #print_backtrace() #} probe module("fuse").function("fuse_writepage"){ page = pointer_arg(1) index = @cast(page, "struct page")->index printf("coming fuse_writepage: %lu \n", index) }probe module("fuse").function("fuse_writepages"){ printf("coming fuse_writepages:\n") }probe module("fuse").function("fuse_send_write_pages"){ printf("coming fuse_send_write_pages:\n") print_backtrace() } #probe kernel.function("write_cache_pages"){ #printf("coming write_cache_pages: \n") #}probe module("fuse").function("fuse_writepage_locked"){ page = pointer_arg(1) index = @cast(page, "struct page")->index printf("coming fuse_writepage_locked: %lu \n", index) print_backtrace() printf("--------------------------------------------------------\n\n\n") }probe module("fuse").function("fuse_writepages_fill"){ page = pointer_arg(1) index = @cast(page, "struct page")->index printf("coming fuse_writepages_fill:%lu \n", index) }probe module("fuse").function("fuse_write_update_size"){ printf("coming fuse_write_update_size: ino %lu size:%lu pos:%lu\n", $inode->i_ino, $inode->i_size, $pos) }#probe module("fuse").function("queue_request") #{ #printf("queue_request ... \n") #print_backtrace() #}#probe kernel.statement("*@mm/page-writeback.c:1941") #{ #if (fuck == 1) { #ino = $page->mapping->host->i_ino #if (ino == g_ino) #printf("coming lock_page: index %lu \n", $done_index) #} #} #probe module("fuse").function("fuse_flush_dirty"){ #inode = pointer_arg(2) #ino = @cast(inode, "struct inode", "")->i_ino #printf("coming fuse_flush_dirty ino: %lu\n", ino) #printf("coming fuse_flush_dirty \n") #}#probe kernel.function("__filemap_fdatawrite_range"){ #mapping = pointer_arg(1) #ino = @cast(mapping, "struct address_space")->host->i_ino #printf("coming __filemap_fdatawrite_range ino: %lu\n", ino ) #}#probe kernel.function("generic_writepages"){ #mapping = pointer_arg(1) #ino = @cast(mapping, "struct address_space")->host->i_ino #printf("coming generic_writepages ino: %lu\n", ino) #}#probe kernel.function("write_cache_pages"){ #mapping = pointer_arg(1) #ino = @cast(mapping, "struct address_space")->host->i_ino #printf("coming write_cache_pages ino: %lu\n", ino) #printf("--------------------------------------------------------\n") #print_backtrace() #printf("--------------------------------------------------------\n\n\n") #}#probe module("fuse").function("fuse_writepage_locked"){ #printf("--------------------------------------------------------\n") #print_backtrace() #printf("--------------------------------------------------------\n\n\n") #}probe end { printf("probe end") }更多脚本可参考(安装完systemtap 就有):/usr/share/systemtap/examples 官网:https://sourceware.org/systemtap/wiki/WarStories

常用方法 1. 列出可以追踪的函数或者变量 查找名字中包含nit的内核函数:
stap -l 'kernel.function("nit")'
查找名字中包含nit的内核函数和变量:
stap -L 'kernel.function("nit")'
自己实验:

列出kernel里面带有write的所有函数
stap -l 'kernel.function("write")' > /tmp/kk
列出fuse里面带有write的所有函数

stap -l 'module("fuse").function("write”)'
2.在脚本里面添加函数,这里面涉及到了如何获取参数: a.最简单的方式:
我们可以先用来获取该函数的可以捕获参数别表,然后直接用$变量名 来获取变量的内容
[root@xt1 systemtap]# stap -L 'kernel.function("do_fork")' kernel.function("do_fork@kernel/fork.c:1685") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $parent_tidptr:int* $child_tidptr:int*test.stp -------------------------------------------------------------------------------------------------------- global proc_counterprobe begin { print("Started monitoring creation of new processes...Press ^C to terminate\n") printf("%-25s %-10s %-11s %-5s %-s\n", "Process Name", "Process ID", "Clone Flags", "start", "size") }probe kernel.function("do_fork") { proc_counter++ printf("%-25s %-10d 0x%-11x %-5lu %lu\n", execname(), pid(), $clone_flags, $stack_start, $stack_size) }probe end { printf("\n%d processes forked during the observed period\n", proc_counter) }上面的函数原型为: long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr)

b. 也可以用第二种方式来获取参数,在缺少debuginfo,即DWARF-less probing的情况下则需要通过uint_arg(),pointer_arg()和ulong_arg()等来获取,这些函数都需要指定当前要获取的参数是第几个参数,编号从1开始。
例如asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)中,uint_arg(1)获取的是fd的值,pointer_arg(2)获取的是buf的地址值,ulong_arg(3)获取的是count参数
c. 如果是通过process.syscall、process("PATH").syscall或者process(PID).syscall来probe系统调用,则可以通过arg1,$arg2等来获取相应的参数值。
3.内联函数或者没有函数可以获取的时候如何获取变量的值
  • 内联函数:对于这种函数我们只能够捕捉函数本身有没有进来,但是到底它的参数和返回值我们无法打印。
  • 无函数可捕获:有时函数使用宏定义的,我们无法捕获这个函数,但是又必须获取它附近相关的变量的值。
    对于上面两种情况,我们可以使出杀手锏“statement”直接定位到源码的某一行来看这个变量!
probe kernel.statement("*@mm/page-writeback.c:1941") { if (fuck == 1) { ino = $page->mapping->host->i_ino if (ino == g_ino) printf("coming lock_page: index %lu \n", $done_index) } }

4.常用函数
1.execname() 获取当前进程的名称,即可执行文件的名称 2. pid() 获取当前进程的PID 3.pp() 获取当前的probe点。例如 probe process.syscall,process.end { /* scripts */},在块中调用pp()可能会返回"process.syscall"和"process.end"。 4.probefunc() 获取当前probe的函数名称。例如probe sys_read函数,在probe块中调用该函数就会返回sys_read。注意这个函数的返回值是从pp()返回的字符串中解析得到的。 5.tid() 获取当前线程的ID 6.cpu() 获取当前CPU的ID 7.gettimeofday_s() 获取当前Unix时间 8.get_cycles() 获取处理器周期数 9.ppfunc() 获取当前probe的函数名称。在probe指定文件中的函数中时非常有用,可以知道当前的probe位于哪个函数。 10.print_backtrace() 打印内核调用栈信息 11.print_ubacktrace() 打印用户态调用栈信息 12.thread_indent() 输出当前线程的信息,格式为“相对时间 程序名称(线程id):(空格)”,如果当前probe的函数执行的次数约到,空格的数量也就越多。这个函数还有一个参数,用来控制空格的数量。如果参数值越大,则空格的数量越多。相对时间是当前的时间(以微秒为单位)减去指定线程第一次执行thread_indent时的时间。 13.target() 获取当前脚本针对的目标进程ID。需要配置stap的-c或-x命令使用。

4. 技巧 a. "."字符窜连接符
如果想将一个函数返回的字符串和一个常量字符串拼接,则在两者之间加入"."即可,例如probefunc()."123"。
"."运算符还支持".=",即拼接后赋值。
b. 获取stap命令行参数
如果要获取命令行参数准确的值,则使用2....$来获取对应的参数。如果想将命令行参数转换为字符串,则使用@1、@2...@来获取参数对应的字符串。
c. next操作
如果在probe函数中,发现某个条件没有满足,则结束本次probe的执行,等待下次事件的到来。示例如下:
global i probe begin { printf("SystemTap Scripts start.....\n"); } probe kernel.function("sys_read") { ++i; if (i % 2) { next; } printf("i = %d\n", i); }

d. $$vars
如果合适的话,可以通过$$vars获取当前所有可见的变量列表。如果列表中某些成员的值显示"?",则表示当前这些变量尚未初始化,还不能访问。
e、call和inline后缀的区别
如果加上call后缀,只有在当前probe的函数是非内联函数时才会触发事件。例如,如果在内联函数pskb_may_pull()的probe点加上call后缀,则事件不会被触发。对于非内联函数的probe点,不能加上inline后缀,否则编译时会报错。如果想触发内联函数的probe事件,一定不能加上call后缀。如果call和inline后缀都不加,则内核函数和非内联函数的probe事件都会触发。
f、输出"%"字符
在systemtap中使用转义字符来输出"%"没有效果,在编译时会报错,可以使用"%%"来输出"%”。
参考:
1.https://blog.csdn.net/zhangskd/article/details/25708441
2.https://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/
3.https://www.jianshu.com/p/84b3885aa8cb

    推荐阅读