簡介
?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)境配置
- ?我的實驗環(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 刪掉然后裝上面新的
- ?安裝工具
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)用,則可以通過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)確的值,則使用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