linux 實用工具----systemtap

簡介

?SystemTap是一個診斷Linux系統(tǒng)性能或功能問題的開源軟件。它使得對運行時的Linux系統(tǒng)進(jìn)行診斷調(diào)式變得更容易、更簡單。有了它,開發(fā)者或調(diào)試人員不再需要重編譯、安裝新內(nèi)核、重啟動等煩人的步驟。

?我們一般寫程序,都會加入相應(yīng)級別的日志,可以幫助我們定位問題或者觀察某些代碼路經(jīng)而不用去使用gdb。但是系統(tǒng)編程,就不能狂打日志(經(jīng)實驗在io路經(jīng)加日志,rsyslog會經(jīng)常掛,而且/var/log/meessages里面的信息過多時查找別的心痛錯誤非常費勁),而且很多調(diào)用棧都處于 kernel space,那么普通的調(diào)試手段就顯得捉襟見肘了。

?此時 systemtap 就能派上用場,他會在內(nèi)核函數(shù)加 probe 探針,對 kernel space 函數(shù)調(diào)用進(jìn)行統(tǒng)計匯總,甚至可以對其進(jìn)行干預(yù)。但是對 user space 調(diào)試支持不是很好。

環(huán)境配置

  1. ?我的實驗環(huán)境是Centos7,內(nèi)核版本kernel-3.10.0-514.26.2.el7.x86_64,根據(jù)版本到官網(wǎng)去下載如下對應(yīng)的包:

[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
官網(wǎng):https://sourceware.org/systemtap/wiki/WarStories

常用方法

1. 列出可以追蹤的函數(shù)或者變量

查找名字中包含nit的內(nèi)核函數(shù):
stap -l 'kernel.function("nit")'
查找名字中包含nit的內(nèi)核函數(shù)和變量:
stap -L 'kernel.function("nit")'

自己實驗:

列出kernel里面帶有write的所有函數(shù)
stap -l 'kernel.function("write")' > /tmp/kk

列出fuse里面帶有write的所有函數(shù)

stap -l 'module("fuse").function("write”)'

2.在腳本里面添加函數(shù),這里面涉及到了如何獲取參數(shù):

a.最簡單的方式:
我們可以先用<stap -L 'kernel.function("do_fork")’>來獲取該函數(shù)的可以捕獲參數(shù)別表,然后直接用$變量名 來獲取變量的內(nèi)容

[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)
}

上面的函數(shù)原型為:
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

b. 也可以用第二種方式來獲取參數(shù),在缺少debuginfo,即DWARF-less probing的情況下則需要通過uint_arg(),pointer_arg()和ulong_arg()等來獲取,這些函數(shù)都需要指定當(dāng)前要獲取的參數(shù)是第幾個參數(shù),編號從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參數(shù)

c. 如果是通過process.syscall、process("PATH").syscall或者process(PID).syscall來probe系統(tǒng)調(diào)用,則可以通過syscall來獲取系統(tǒng)調(diào)用號,通過arg1,$arg2等來獲取相應(yīng)的參數(shù)值。

3.內(nèi)聯(lián)函數(shù)或者沒有函數(shù)可以獲取的時候如何獲取變量的值

  • 內(nèi)聯(lián)函數(shù):對于這種函數(shù)我們只能夠捕捉函數(shù)本身有沒有進(jìn)來,但是到底它的參數(shù)和返回值我們無法打印。
  • 無函數(shù)可捕獲:有時函數(shù)使用宏定義的,我們無法捕獲這個函數(shù),但是又必須獲取它附近相關(guān)的變量的值。
    對于上面兩種情況,我們可以使出殺手锏“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.常用函數(shù)

1.execname()
獲取當(dāng)前進(jìn)程的名稱,即可執(zhí)行文件的名稱
2. pid()
獲取當(dāng)前進(jìn)程的PID
3.pp()
獲取當(dāng)前的probe點。例如 probe process.syscall,process.end { /* scripts */},在塊中調(diào)用pp()可能會返回"process.syscall"和"process.end"。
4.probefunc()
獲取當(dāng)前probe的函數(shù)名稱。例如probe sys_read函數(shù),在probe塊中調(diào)用該函數(shù)就會返回sys_read。注意這個函數(shù)的返回值是從pp()返回的字符串中解析得到的。
5.tid()
獲取當(dāng)前線程的ID
6.cpu()
獲取當(dāng)前CPU的ID
7.gettimeofday_s()
獲取當(dāng)前Unix時間
8.get_cycles()
獲取處理器周期數(shù)
9.ppfunc()
獲取當(dāng)前probe的函數(shù)名稱。在probe指定文件中的函數(shù)中時非常有用,可以知道當(dāng)前的probe位于哪個函數(shù)。
10.print_backtrace()
打印內(nèi)核調(diào)用棧信息
11.print_ubacktrace()
打印用戶態(tài)調(diào)用棧信息
12.thread_indent()
輸出當(dāng)前線程的信息,格式為“相對時間 程序名稱(線程id):(空格)”,如果當(dāng)前probe的函數(shù)執(zhí)行的次數(shù)約到,空格的數(shù)量也就越多。這個函數(shù)還有一個參數(shù),用來控制空格的數(shù)量。如果參數(shù)值越大,則空格的數(shù)量越多。相對時間是當(dāng)前的時間(以微秒為單位)減去指定線程第一次執(zhí)行thread_indent時的時間。
13.target()
獲取當(dāng)前腳本針對的目標(biāo)進(jìn)程ID。需要配置stap的-c或-x命令使用。

4. 技巧

a. "."字符竄連接符
如果想將一個函數(shù)返回的字符串和一個常量字符串拼接,則在兩者之間加入"."即可,例如probefunc()."123"。
"."運算符還支持".=",即拼接后賦值。
b. 獲取stap命令行參數(shù)
如果要獲取命令行參數(shù)準(zhǔn)確的值,則使用1、2....$<NN>來獲取對應(yīng)的參數(shù)。如果想將命令行參數(shù)轉(zhuǎn)換為字符串,則使用@1、@2...@<NN>來獲取參數(shù)對應(yīng)的字符串。
c. next操作
如果在probe函數(shù)中,發(fā)現(xiàn)某個條件沒有滿足,則結(jié)束本次probe的執(zhí)行,等待下次事件的到來。示例如下:

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獲取當(dāng)前所有可見的變量列表。如果列表中某些成員的值顯示"?",則表示當(dāng)前這些變量尚未初始化,還不能訪問。
e、call和inline后綴的區(qū)別
如果加上call后綴,只有在當(dāng)前probe的函數(shù)是非內(nèi)聯(lián)函數(shù)時才會觸發(fā)事件。例如,如果在內(nèi)聯(lián)函數(shù)pskb_may_pull()的probe點加上call后綴,則事件不會被觸發(fā)。對于非內(nèi)聯(lián)函數(shù)的probe點,不能加上inline后綴,否則編譯時會報錯。如果想觸發(fā)內(nèi)聯(lián)函數(shù)的probe事件,一定不能加上call后綴。如果call和inline后綴都不加,則內(nèi)核函數(shù)和非內(nèi)聯(lián)函數(shù)的probe事件都會觸發(fā)。
f、輸出"%"字符
在systemtap中使用轉(zhuǎn)義字符來輸出"%"沒有效果,在編譯時會報錯,可以使用"%%"來輸出"%”。

參考:
1.https://blog.csdn.net/zhangskd/article/details/25708441
2.https://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/
3.http://www.itdecent.cn/p/84b3885aa8cb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • systemtap工具的安裝 準(zhǔn)備工作 uname -a 查看當(dāng)前內(nèi)核版本是哪一個,然后使用 yum instal...
    marshalzxy閱讀 7,935評論 0 0
  • 霸爺博客,干貨滿滿。有兩篇文章現(xiàn)在還記得,《Linux下如何知道文件被哪個進(jìn)程寫》和《巧用Systemtap注入延...
    董澤潤閱讀 9,812評論 0 4
  • 背景: 特殊需求,需要使用kernel 4.x版本,在云上ECS如何更換呢? 慫一下先: 請對生產(chǎn)環(huán)境,保持敬畏...
    玲小喵閱讀 230評論 0 0
  • 1,安裝systemtap和kernel-devel包 yum install systemtap kernel-...
    rich_linn閱讀 4,654評論 0 0
  • 昨天去看了這部刷爆我朋友圈的電影。哎。 小說作者和編劇張嘉佳非誠勿擾前任男嘉賓,發(fā)表起意見來一套一套的,像極了十幾...
    六道這張嘴閱讀 458評論 0 0

友情鏈接更多精彩內(nèi)容