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脚本

一个简单的例子如下:

#!/usr/bin/env stap

global fuck = 0
global g_ino = 0

probe 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")->index

   printf("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 %lu  end: %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_counter

probe 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

你可能感兴趣的:(linux 实用工具----systemtap)