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,根据版本到官网去下载如下对应的包:

[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", "<linux/fs.h>")->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.最简单的方式:
我们可以先用<stap -L 'kernel.function("do_fork")’>来获取该函数的可以捕获参数别表,然后直接用$变量名 来获取变量的内容

[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系统调用,则可以通过syscall来获取系统调用号,通过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命令行参数
如果要获取命令行参数准确的值,则使用1、2....$<NN>来获取对应的参数。如果想将命令行参数转换为字符串,则使用@1、@2...@<NN>来获取参数对应的字符串。
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容