203. systemtap stap命令

安装

Systemtap Installation

1
sudo apt-get install -y systemtap gcc

Where to get debug symbols for kernel X?

GPG key import

  • 16.04 and higher

    1
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C8CAB6595FDFF622
  • older distributions

    1
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ECDCAD72428D7C01

Add repository config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
codename=$(lsb_release -c | awk  '{print $2}')
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename} main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF

sudo apt-get update
sudo apt-get install ubuntu-dbgsym-keyring

stap-prep
# 如果需要安装linux-head,会显示安装linux-header,如:Please install linux-headers-4.15.0-20-generic
sudo apt-get install linux-headers-4.15.0-20-generic

sudo apt-get install ubuntu-dbgsym-keyring
sudo apt-get install linux-image-$(uname -r)-dbgsym

 参考:https://wiki.ubuntu.com/Kernel/Systemtap

简介

概览

介绍

  • systemtap是一个强大的调试工具,一种调试语言
  • 支持脚本编程,“tapset”,更简洁,更高效

特点:

  • Flexibility:稳定,提供问题的接口,方便用户扩展
  • Easeofuse:动态监控,不影响原程序运行

官方链接:https://sourceware.org/systemtap

systemtap 典型应用场景

  • 定位(内核)函数位置
  • 查看函数被调用时的调用堆栈、局部变量、参数
  • 查看函数指针变量实际指的是哪个函数
  • 查看代码的执行轨迹(哪些行被执行了)
  • 查看内核或者进程的执行流程
  • 调试内存泄露或者内存重复释放
  • 统计函数调用次数

官方链接:https://sourceware.org/systemtap/tapsets/index.html

如何使用systemtap

stap 命令

常用参数:

参数 说明
-e SCRIPT Run given script.
-l PROBE List matching probes.
-L PROBE List matching probes and local variables.
-g Guru mode
-D NM=VAL emit macro definition into generated C code
-o FILE send script output to file, instead of stdout.
-x PID sets target() to PID

注意安装内核调试包

3种执行方式

从标准输入中读入并运行脚本

1
$ stap [选项]

如: 定位函数位置: stap -l 'kernel.function("vfs_open")'

linux源码:https://elixir.bootlin.com/linux/v6.5/source/fs/open.c#L1045

运行命令行中的脚本

1
stap [选项] -e 脚本

示例

1
stap -e 'probe begin { log("hell world") exit() }'

常用选项

  • -v: 展开显示脚本解析、细化、翻译C、编译C、加载内核模块整个过程

    1
    stap -ve 'probe begin { log("hell world") exit() }'
  • -k: 保存编译期间的中间文件

    1
    stap -ke 'probe begin { log("hell world") exit() }'
  • -o: FILE 输出到文档,而不是输出到标准输出

  • -p NUM: 运行完Pass Num后停止,缺省是运行到 Pass 5

    1
    stap -p 2 -kve 'probe begin { log("hell world") exit()}'
  • -g: 采用guru模式,允许脚本中嵌入C语句;

  • -c CMD: 启动探测后,运行CMD命令,直到命令结束后退出;

  • -x pid: 使用target()捕获指定的进程ID (-x procss_id 指定探测进程ID为’process_id‘的程序)

实例:systemtap 是如何运行的?

Systemtap 的处理流程有5个步骤:

  • 解析 script 文件(Parse)
  • 细化(Elaborate)
  • Script 文件翻译成 C 语言代码(Translate)
  • 编译 C 语言代码(生成内核模块)(Compile)
  • 加载内核模块(Run)

执行.stp脚本文件

执行stap文件命令

1
2
3
stap[选项] xx.stp

stap[选项] ./xx.stp

示例

1
stap isprime_test.stp

常用选项

  • -o: FILE 输出到文档,而不是输出到标准输出

    1
    stap -o outfile.log isprime_test.stp
  • -S size,count: 限制输出文件的大小(单位MB)和最大的文件数。这个选项会实现一个回滚机制的日志输出,日志文件名会加一个序列号的后缀。

    1
    stap -o outfile.log -S 1,2 isprime_test.stp
  • -F: 使用SystemTap的长期记录模式,脚本后台执行。

  • -x process_id: 设置SystemTap处理函数target()指向特定进程号

脚本语法

systemtap 脚本文件是 .stp 后缀的文件,使用的脚本语言是前面讲到的 systemtap 自己定义的脚本语言,一个 systemtap 脚本描述了将要探测的探测点以及定义了相关联的处理函数,每一个探测点对应于一个内核函数或事件或函数内部的某一位置。被关联的处理函数将在内核执行到对应的探测点时被执行。

systemtap 脚本包含两个基本元素:eventhandler。Systemtap 执行脚本时,它会监控事件(event),当事件发生时,Linux 内核就会执行 handler, 其中:

  • 事件(event)的类型有开始/结束、定时器超时、会话终止等。
  • Handler 是指定事件发生时需要做的一些脚本语句,我们可以在这里指定为各类维测或者想跟踪的信息。

关键字:

  • probe: 探测,是 systemtap 进行具体地收集数据的关键字。
  • probe point:是 probe 动作的时机,也称探测点,是 probe 程序监视的某事件点,一旦侦测的事件(event)触发了,则 probe 将从此处插入内核或者用户进程中。
  • probe handler:是当 probe 插入内核或者用户进程后所做的具体动作。

probe 的用法如下:

1
probe probe-point { statement }

数据结构

  • 整数(integers)
  • 字符串(strings)
  • 关联数组(associative Arrays)

控制结构

变量

无需声明, 上下文自动推测和检查

全局变量: 使用 globle 修饰

语句

语句分隔符 ; 是可选的

探测probe

使用kprobe提供的接口来实现探测,对于每一个探测,需要定义探测点以及相应的处理函数

探针语法

  • 内核的探测点语法:

    • kernel.function(pattern): 在内核函数的入口处放置探测点,可以获取参数 $parm

    • kernel.function(pattern).return: 在内核函数返回时的出口处放置探测点,可以获取返回时的参数$parm

    • kernel.function(PATTERN).return.maxactive(VALUE)

    • kernel.function(pattern).call: 内核函数的调用入口处放置探测点,获取对应函数信息

    • kernel.fuction(pattern).inline: 获取符合条件的内联函数

    • kernel.function(pattern).exported: 只选择导出的函数

    • kernel.function(PATTERN).label(LPATTERN)

    • module(modulename).fuction(pattern): 在模块modulename中调用的函数入口处放置探测点

    • module(modulename).fuction(pattern).return: 在模块module中调用的函数返回时放置探测点

    • module(modulename).fuction(pattern).return.maxactive(VALUE)

    • module(modulename).fuction(pattern).call: 在模块modulename中调用的函数入口处放置探测点

    • module(modulename).fuction(pattern).inline: 在模块modulename中调用的内联函数处放置探测点

    • kernel.statement(pattern): 在内核中的某个地址处增加探针(函数、文件行号)

    • kernel.statement(pattern).absolute: 在内核中的某个地址处增加探针(函数、文件行号),精确匹配地址

    • module(modulename).statement(pattern): 在内核模块中的某个地址处增加探针(函数、文件行号)

  • 用户态程序的探测点语法

    • process(PROCESSPATH).function(PATTERN)
    • process(PROCESSPATH).function(PATTERN).call
    • process(PROCESSPATH).function(PATTERN).return
    • process(PROCESSPATH).function(PATTERN).inline
    • process(PROCESSPATH).statement(PATTERN)

语法解释说明:

  • return: 表示返回点探测
  • return.maxactive(VALUE): 修饰 return,控制同时探测多少个实例,默认足够一搬不用,如果出现了跳过探测现象且很多,可以使用此参数,提升探测效果
  • Call: 表示函数被调用时触发此调用点
  • Inline: 表示内联函数需要展示时候用此参数
  • Label: 表示内核常常用到 goto 函数,用此标签可以探测出具体的 goto 返回点
  • Statement: 定位到具体的行或者函数,将这些定位点作为跟踪点

PATTERN语法:

1
2
3
func[@file]

func@file:linenumber

示例:

  1. kernel.function("*init*"): 表示对内核中包含有 init 的函数进行探测

  2. module(“ext3”).function(“*”): 表示对 ext3 内核模块的所有函数进行探测

  3. kernel.statement("cmdline_proc_show@fs/proc/cmdline.c:9"): 表示对 cmdline_proc_show 函数中的 /fs/proc/cmdline.c 文件的第9行进行探测 ==???没看懂==

  4. process("/home/tianyu/chmod").function("GetUidGid"): 表示对用户态程序 /home/tianyu/chmod 的函数 GetUidGid 进行探测

探针名称

探针名称 探针含义
begin 脚本开始时触发
end 脚本结束时触发
kernel.function("sys_read") 调用sys_read时触发
kernel.function("sys_read").call 同上
kernel.function("sys_read").return sys_read执行完,返回时触发
syscall.* 调用任何系统调用时触发
kernel.function("*@kernel/fork.c:934") 执行到 fork.c 的934行时触发
module(“ext3”).function(“ext3_file_write”) 调用ext3模块中的ext3_file_write时触发
timer.jiffies(1000) 每隔1000个内核jiffy时触发一次
timer.ms()
timer.s()

Systemtap黑名单

Systemtap包含了一个黑名单,其中列出的函数不能被Systemtap探测,因为它们会导致无限探测循环、锁重入等问题

所有的脚本内容在转换时进行严格的检查,并且在运行时也要检查(如无限循环、内存使用、递归和无效指针等)

API函数

tapsets 是一个脚本库,包含了许多 tapset,每一个 tapset 一般为某一内核子系统或特定的功能块预定义了一套探测点、辅助函数或全局变量供用户脚本或其它的 tapset 引用,它定义的一些数据能够被每一个探测点处理函数或脚本使用

函数 说明
execname() 获取当前进程名称
pid() 当前进程的ID
tid() 当前线程ID
cpu() 当前cpu号
gettimeofday_s() 获取当前系统时间,秒
gettimeofday_usec() 获取当前系统时间,微秒
ppfunc() 获取当前probe的函数名称,可以知道当前probe位于哪个函数
print_backtrace() 打印内核函数调用栈

变量用法

变量格式 使用
$varname 引用变量varname
$var->field 引用结构的成员变量
$var[N] 引用数组的成员变量
&$var 变量的地址
@var(“varname”) 引用变量varname
@var(“var@src/file.c”) 引用src中file.c编译时的全局变量
@var(“var@src/file.c”)->field src/file.c中全局结构的成员变量
@var(“var@src/file.c”)[N] src/file.c中全局数据变量
&@var(“var@src/file.c”) 引用变量的地址
$var$ 将变量转为字符串类型
$$vars 包含函数所有参数,局部变量,需以字符串类型输出
$$locals 包含函数所有局部变量,需以字符串类型输出
$$params 包含所有函数参数的变量,需以字符串类型输出

systemtap使用技巧

跟踪内核态调用栈和入参

systemtap 提供库函数,分别用于跟踪用户态和内核态的调用堆栈。

  • 用户态:(不带 s 和带 s 的区别是前者直接输出,后者是返回堆栈字符串)

    • print_ubacktrace()
    • sprint_ubacktrace()
  • 内核态:

    • print_backtrace()
    • sprint_backtrace()

跟踪对文件的 open 流程,更多调用栈和入参,提升内核代码分析和调试的效率

更多跟踪函数:https://linux.die.net/man/5/stapfuncs

示例:

  1. 查看 do_sys_open 位置

    1
    2
    root@ubuntu18:/tmp/stapw0gXSN# stap -L 'kernel.function("do_sys_open")'
    kernel.function("do_sys_open@/build/linux-5s7Xkn/linux-4.15.0/fs/open.c:1049") $dfd:int $filename:char const* $flags:int $mode:umode_t $op:struct open_flags
  2. vfsopen.stp脚本文件

  3. 运行vfsopen.stp脚本文件

跟踪整个数据结构

systemtap 有两个语法可以输出整个数据结构:

在变量的后面加一个或者两个$号即可,输出的内容是字符串,所以打印的时候需要用 %s

跟踪用户态

利用 systemtap 可以轻松跟踪用户态程序和 so 的调用轨迹、参数等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/stap
probe begin {
printf("=============begin============\n")
}
probe process("/usr/lib/x86_64-linux-gnu/libc.so.6").function("malloc") {
printf("%s pid=%d malloc(%s) \r\n", execname(), pid(), $$parms)
print_ubacktrace()
}
probe timer.ms(10000)#1s later {
log("probe exit")
exit()
}
probe end {
printf("=============end============\n")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@ctumhispra00449:/home/tianyu/stap# ./userspace.stp
=============begin============
sh pid=1067447 malloc(bytes=0x28)
0x7f95d869a230 : __libc_malloc+0x0/0x310 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d8910c4a [/usr/lib/x86_64-linux-gnu/ld-2.31.so+0xfc4a/0x2c000]
sh pid=1067447 malloc(bytes=0xa000)
0x7f95d869a230 : __libc_malloc+0x0/0x310 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d87f3589 [/usr/lib/libachk.so+0x4589/0x106000]
sh pid=1067447 malloc(bytes=0x1d8)
0x7f95d869a230 : __libc_malloc+0x0/0x310 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d8682a7e : _IO_fopen@@GLIBC_2.2.5+0x1e/0x100 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d87f35c9 [/usr/lib/libachk.so+0x45c9/0x106000]
sh pid=1067447 malloc(bytes=0x1000)
0x7f95d869a230 : __libc_malloc+0x0/0x310 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d8681e54 : _IO_file_doallocate+0x94/0x160 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d8692020 : _IO_doallocbuf+0x50/0xc0 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d8690df4 : _IO_file_underflow@@GLIBC_2.2.5+0x284/0x360 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d86920d6 : _IO_default_uflow+0x36/0x60 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d86839bc : _IO_getline_info+0xac/0x1a0 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]
0x7f95d868281a : _IO_fgets+0x9a/0x190 [/usr/lib/x86_64-linux-gnu/libc-2.31.so]

简单实例

打印素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/stap

probe begin {
printf("=============begin============\n")
}


function isprime(x) {
if (x < 2) return 0
for (i = 2; i < x; i++) {
if (x % i == 0) return 0
if (i * i > x) break
}
return 1
}

probe begin {
for (i = 0; i < 50; i++)
if (isprime(i)) printf("%d, pid(%d)\n", i, pid())
exit()
}

probe end {
printf("=============end============\n")
}

通过 stap 脚本实现对内核接口调用点的跟踪, stap 脚本名为 hellowolrd.stp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/stap

probe begin {
log("begin probe")
}

probe kernel.function("vfs_write").call {
printf("%s(%d) vfs_write (%s)\n",execname(),pid(),print_regs())
print_backtrace()
}

probe timer.ms(5000)#5s later {
log("probe exit")
exit()
}

probe end {
log("end probe")
}

systemtap 技术

systemtap 基本思想是命名事件,并为它们提供处理程序。每当发生指定的事件时,内核都会将处理程序视为子例程运行,然后继续运行。处理程序是一系列脚本语言语句,用于指定事件发生时要完成的工作。

systemtap 基本原理是将脚本翻译成 C 语言,执行 C 编译器创建一个内核模块。当模块被加载后,通过挂载到内核来激活所有的探测事件。

然后,当事件发生在任何处理器上时,编译后的处理程序就会运行。

最终,systemtap 会话停止,hook 取消,内核模块被移除,整个过程由命令行程序 stap 驱动。

用户手册: https://sourceware.org/systemtap/documentation.html

典型应用场景库 193个: https://sourceware.org/systemtap/examples/

基本应用

定位函数位置

查找内核函数的定义位于哪一个文件的哪一行时很有用,特别是有些函数有多个定义的时候(不同的架构或者宏会来控制当前使用哪一个,实际上只会有一个),用 systemtap 一行命令就可以搞定。

比如说我们查看内核的 vfs_open 函数在哪里定义的:

可以在 open.c 文件的第862行定义的。

同时支持*号来实现模糊查找

-L 查看变量,例如 vfs_open 的两个参数:

其他的一些用法:

1
2
3
4
5
6
7
8
stap -l 'kernel.function("vfs_open")'
stap -l 'kernel.function("*open")'

stap -L 'kernel.function("vfs_open")'

stap -l 'process("/lib/x86_64-linux-gnu/libc.so.6").function("printf")'

stap -l 'module("ceph").function("ceph_statfs")'

查看文件能够添加探针的位置(对比源代码)

1
stap -L 'kernel.statement("*@fs/statfs.c")'

打印函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//在调用vfs_statfs处添加探针
probe kernel.function("vfs_statfs") {
//打印调用vfs_statfs函数的进程名称以及进程号
printf("%s %d\n", execname(),pid());
//打印path参数的结构体成员,以及整个buf结构体
printf("path : %s buf : %s\n",$path->mnt->mnt_root->d_iname$,$buf$);

// 如果没有exit(), 那么程序就一直运行, 直至用户自己ctrl+c关闭
exit()
}

probe end {
printf("end to probe\n");
}

打印函数局部变量

函数实现

查看结果

查看error在函数statfs_by_dentry执行完成之后的结果,那么就在下一行处添加探针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//探测statfs.c中的第75行
probe kernel.statement("vfs_statfs@fs/statfs.c:75") {
printf("%s %d\n", execname(),pid());
printf("error number is %d\n",$error);
}

probe timer.s(10) #100ms later
{
log("probe exit")
exit()
}

probe end {
printf("end to probe\n");
}

修改函数局部变量(慎重)

将上一个打印的变量的值从0 更改为其他的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

probe kernel.statement("vfs_statfs@fs/statfs.c:75") {
printf("%s %d\n", execname(),pid());
printf("error number before modify is %d\n",$error);
$error=$1
printf("error number before modify is %d\n",$error);
}

probe end {
printf("end to probe\n");
}

执行stap -g test.stp 2将2传入,但是运行的时候需要增加-g参数

打印函数返回时的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//在调用vfs_statfs处添加探针
probe kernel.function("vfs_statfs").return {
//打印调用vfs_statfs函数的进程名称以及进程号
printf("%s %d\n", execname(),pid());
//打印局部变量需使用@entry($varname)
printf("error's return value is %d\n", @entry($error));
}

probe end {
printf("end to probe\n");
}

打印函数调用栈

1
2
3
4
5
6
7
8
#!/usr/bin/stap

probe kernel.function("vfs_statfs") {
printf("%s %d\n", execname(),pid());
printf("----------------------------------\n");
print_backtrace();//打印vfs_statfs在内核态的调用栈
printf("----------------------------------\n");
}

执行时增加参数 --all-modules, 类似如: stap --all-modules test.stp, 探测所有的系统模块

嵌入C代码

获取系统调用了vfs_statfs函数的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/stap
global count = 0;//全局变量

//C函数计算次数
function getcount:long(task:long) %{
int count = (int) STAP_ARG_task;
count ++;
STAP_RETURN(count);
%}

probe begin {
printf("begin to probe\n");
}

probe kernel.function("vfs_statfs") {
printf("%s %d\n", execname(),pid());
printf("----------------------------------\n");
print_ubacktrace();
printf("----------------------------------\n");
count = getcount(count);
printf("c code caculate the count is %d\n", count);
}

probe end {
printf("end to probe\n");
}
  1. 格式上:C语言代码要在每个大括号前加%前缀,是%{…… %} 而不是%{ …… }%
  2. 获取脚本函数参数要用STAP_ARG_前缀,即getcount函数使用的是STAP_ARG_task来获取传入的count参数
  3. 一般long等返回值用STAP_RETURN,一般字符串则使用snprintf, strncat等方式把字符串复制到STAP_RETVALUE里面
1
stap -g test.stp

追踪函数流程

函数被哪个进程调用,且在该函数处执行了多长时间

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/stap

probe begin {
printf("begin to probe");
}

probe kernel.function("sys_read").call {
printf("%s -> %s\n",thread_indent(4),ppfunc());
}

probe kernel.function("sys_read").return {
printf("%s <- %s\n",thread_indent(-4),ppfunc());
}

其中thread_indent()函数为/usr/share/systemtap/tapset/indent.stp中实现的一个stap脚本,该函数的功能是增加函数执行时间(微妙),进程名称(pid)打印出来,传入的参数是打印空格的个数

跟踪特定进程

跟踪一个进程所调用过的函数:sshd进程调用的系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//探测所有的系统调用
probe syscall.* {
procname = execname();

if (procname =~ "sshd.*"){ //使用stp脚本中的通配符匹配所有的sshd服务的子进程
printf("%s[%d]: %s -> %s\n", procname,pid(),name,ppfunc()); //name为sshd内部函数,ppfunc为该函数调用的系统调用
}
}

查看代码执行路径

想要知道当前系统针对该函数的处理过程,走到哪个分支

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//探测点设置为*
probe kernel.statement("do_statfs_native@fs/statfs.c:*") {
printf("%s\n",pp());//打印当前探测点
}

输出:

1
2
3
4
5
root@ubuntu18:~/stap_scipts# stap show_run_flow.stp
begin to probe
kernel.statement("do_statfs_native@/build/linux-5s7Xkn/linux-4.15.0/fs/statfs.c:111")
kernel.statement("do_statfs_native@/build/linux-5s7Xkn/linux-4.15.0/fs/statfs.c:149")
kernel.statement("do_statfs_native@/build/linux-5s7Xkn/linux-4.15.0/fs/statfs.c:150")

查看内核文件函数的执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//监控mds_client.c文件中所有的函数,调用时打印
probe module("ceph").function("*@mds_client.c").call {
printf("%s -> %s \n", thread_indent(4), ppfunc());
}

//监控mds_client.c文件中所有的函数,返回时打印
probe module("ceph").function("*@mds_client.c").return {
printf("%s <- %s \n", thread_indent(-4), ppfunc());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/stap

probe begin {
printf("begin to probe\n");
}

//监控mds_client.c文件中所有的函数,调用时打印
probe kernel.function("*@statfs.c").call{
printf("%s -> %s \n", thread_indent(4), ppfunc());
}

//监控mds_client.c文件中所有的函数,返回时打印
probe kernel.function("*@statfs.c").return {
printf("%s <- %s \n", thread_indent(-4), ppfunc());
}