SystemTap 是一个 Linux 非常有用的调试(跟踪 /探测)工具,常用于 Linux
内核或者应用程序的信息采集,比如:获取一个函数里面运行时的变
量、调用堆栈,甚至可以直接修改变量的值,对诊断性能或功能问题非
常有帮助。SystemTap 提供非常简单的命令行接口和很简洁的脚本语
言,以及非常丰富的 tapset 和例子。
定位(内核)函数位置
查看函数被调用时的调用堆栈、局部变量、参数
查看函数指针变量实际指的是哪个函数
查看代码的执行轨迹(哪些行被执行了)
查看内核或者进程的执行流程
调试内存泄露或者内存重复释放
统计函数调用次数
......
在网上找了个原理图:
SystemTap 的处理流程有 5 个步骤:解析 script 文件(parse)、细化( elaborate )、script 文件翻译成 C 语言代码( translate )、编译 C 语言代码(生成内核模块)( build )、加载内核模块( run )
SystemTap 依赖的 package: elfutils、gcc、kernel-devel、kernel-debuginfo 如果调用用户态进程,还需要该程序有调试符号,否则无法调试。 推荐使用最新稳定版的 SystemTap,目前最新稳定版为:systemtap-2.9.tar.gz
5.1 stap 命令
stap [OPTIONS] FILENAME [ARGUMENTS]
stap [OPTIONS] - [ARGUMENTS]
stap [OPTIONS] – e SCRIPT [ARGUMENTS]
比较常用和有用的参数:
-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
Hello World:
root@j9 ~/stp# cat hello-world.stp
probe begin {
print("===Hello World===\n")
}
probe end {
print("===GunLe===\n")
}
root@j9 ~/stp# stap hello-world.stp
===Hello World===
^C===GunLe===
root@j9 ~/stp# stap -e 'probe begin { printf("Hello World!\n") exit() }'
Hello World!
root@j9 ~/stp#
5.2 staprun 命令
staprun [OPTIONS] MODULE [MODULE-OPTIONS]
stap 命令与 staprun 命令的区别在于: stap 命令的操作对象是 stp 文件或 script 命令等,而 staprun 命令的操作对象是编译生成的内核模块。
6.1 probe
“ probe ” <=> “探测”, 是 SystemTap 进行具体地收集数据的关键字。
“ probe point ” 是 probe 动作的时机,也称探测点。也就是 probe 程序监视的某事件点,一旦侦测的事件触发了,则 probe 将从此处插入内核或者用户进程中。 “ probe handle ” 是当 probe 插入内核或者用户进程后所做的具体动作。
probe 用法:
probe probe-point { statement }
在 Hello World 例子中 begin 和 end 就是 probe-point,statement 就是该探测点的处理逻辑,在 Hello World 例子中 statement 只有一行 print,statement 可以是复杂的代码块。 探测点语法:
kernel.function(PATTERN)
kernel.function(PATTERN).call
kernel.function(PATTERN).return
kernel.function(PATTERN).return.maxactive(VALUE)
kernel.function(PATTERN).inline
kernel.function(PATTERN).label(LPATTERN)
module(MPATTERN).function(PATTERN)
module(MPATTERN).function(PATTERN).call
module(MPATTERN).function(PATTERN).return.maxactive(VALUE)
module(MPATTERN).function(PATTERN).inline
kernel.statement(PATTERN)
kernel.statement(ADDRESS).absolute
module(MPATTERN).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)
PATTERN 语法为:
func[@file]
func@file:linenumber
例如:
kernel.function("*init*")
module("ext3").function("*")
kernel.statement("*@kernel/time.c:296")
process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request")
在 return 探测点可以用$return 获取该函数的返回值。 inline 函数无法安装.return 探测点,也无法用$return 获取其返回值。
6.2 基本语法
SystemTap 脚本语法比较简单,与 C 语言类似,只是每一行结尾";"是可选的。主要语句如下: if/else、while、for/foreach、break/continue、return、next、delete、try/catch 其中: next:主要在 probe 探测点逻辑处理中使用,调用此语句时,立刻从调用函数中退出。不同于 exit()的是,next 只是退出当前的调用函数,而此 SystemTap 并没有终了,但 exit()则会终止 SystemTap。
6.2.1 变量
不需要明确声明变量类型,脚本语言会根据函数参数等自动判断变量是什么类型的。 局部变量:在声明的 probe 和 block (”{ }“范围内的部分)内有效。 全局变量:用” global “声明的变量,在此 SystemTap 的整个动作过程中都有效。全局变量的声明位置没有具体要求。需要注意的是,全局变量默认有锁保护,使用过多会有性能损失,如果用全局变量保存指针,可能出现指针所指的内容被进程修改,在探测点中拿不到真正的数据。 获取进程中的变量(全局变量、局部变量、参数)直接在变量名前面加$即可(后面会有例子)
6.2.2 注释
# ...... :Shell 语言风格
//...... :C++语言风格
/*......*/ :C 语言风格
6.2.3 操作符
比较运算符、算数运算符基本上与 C 语言一样,需要特别指出的是: (1)、.操作符:连接两个字符串,类似于 php ; (2)、=~和!~:正则匹配和正则不匹配;
6.2.4 函数
函数定义例子:
function indent:string (delta:long){
return _generic_indent(-1, "", delta)
}
function _generic_indent (idx, desc, delta)
{
ts = __indent_timestamp ()
if (! _indent_counters[idx]) _indent_timestamps[idx] = ts
depth = _generic_indent_depth(idx, delta)
return sprintf("%6d (%d:%d) %s:%-*s", (ts - _indent_timestamps[idx]), depth, delta, desc, depth, "")
}
function strlen:long(s:string) %{
STAP_RETURN(strlen(STAP_ARG_s));
%}
官方有很多很有用的函数,详情请参考: https://sourceware.org/systemtap/tapsets/ 以及在本机安装了 SystemTap 之后在目录 /usr/local/share/systemtap/tapset/下也可以看具体函数的实现以及一些奇特的用法。
本文是阿里云 CDN 安防专家金九所写,下次会发布 SystemTap 使用技巧之二,欢迎沟通交流~