1. 简介
SystemTap是一个Linux非常有用的调试(跟踪/探测)工具,常用于Linux内核或者应用程序的信息采集。
比如:获取一个函数里面运行时的变量、调用堆栈,甚至可以直接修改变量的值,对诊断性能或功能问题非常有帮助
2. 何时使用
定位(内核)函数位置
查看函数被调用时的调用堆栈、局部变量、参数
查看函数指针变量实际指的是哪个函数
查看代码的执行轨迹(哪些行被执行了)
查看内核或者进程的执行流程
调试内存泄露或者内存重复释放
统计函数调用次数
嵌入探针里面执行代码(比如c代码),可以改变变量
如果是独立的%{%},配合#include头文件,可以写正常的c函数定义与实现,但要被探针调用还要使用systemtap支持的方式
限制:
参数与返回值long/sting
支持在探针的位置,插入代码,由于是以3号中断的方式执行,所以没法在探针位置执行跳转的goto/returen/break/continue等
3. 原理


SystemTap的处理流程有5个步骤:解析script文件(parse)、细化(elaborate)、script文件翻译成C语言代码(translate)、编译C语言代码(生成内核模块)(build)、加载内核模块(run)


4. 安装
systemtap-client systemtap-devel systemtap-runtime
内核包:
kernel-3.10.0-229.el7.x86_64.rpm
kernel-devel-3.10.0-229.el7.x86_64.rpm
kernel-debuginfo-common-x86_64-3.10.0-229.el7.x86_64.rpm
kernel-debuginfo-3.10.0-229.el7.x86_64.rpm
注意内核的rpm -qpi/-qi 编译时间是否一致, 不一致会出现很多问题
5. 入门
5.1 资料
安装后,可以通过一下方式获取示例或者帮助 man stap /usr/share/systemtap/
5.2 示例
stap -e 'probe begin{printf("Hello, World"); exit();}'
Hello, World
stap -v my_hello.stp //打印某个文件的所有函数的调用
probe kernel.function("*@net/core/neighbour.c")
{
printf("%s\n", ppfunc())
}
//打印某个文件(neighbour.c)中某个函数(neigh_update)支持的探针位置
//目前看探针位置的代码是还未执行,所以要插入查询的代码,应该在对应行号之后。
stap -L 'kernel.statement("neigh_update@net/core/neighbour.c:*")'
//打印某个探针位置对应的可访问变量
stap -L 'kernel.function("neigh_update@net/core/neighbour.c:*"")'
//编译ko,到目的机器执行staprun
stap -p4 -v my_hello.stp -m my_hello
cp ./my_hello.ko /lib/modules/3.10.0-229.el7.x86_64/systemtap/
staprun my_hello
5.3 常用参数说明
比较常用和有用的参数: -e SCRIPT Run given script. -l PROBE List matching probes. -L PROBE List matching probes and local variables. -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 -p NUM 执行几个阶段,正常包括5个阶段 parse, elaborate, translate, compile,run -g guru mode如果stp脚本有嵌入c代码的话,需要添加的参数
6. 脚本语言
6.1 probe
“probe” <=> “探测”, 是SystemTap进行具体地收集数据的关键字。


“probe point” 是probe动作的时机,也称探测点。也就是probe程序监视的某事件点,一旦侦测的事件触发了,则probe将从此处插入内核或者用户进程中。
“probe handle” 是当probe插入内核或者用户进程后所做的具体动作。
用法:
probe probe-point { statement }
探测点语法 PATTERN对应 func[@file] 或者 func@file:linenumber
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)
示例:
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获取返回值,示例:固定的让某个函数的返回值为1
stap -g -e 'probe kernel.function("devmem_is_allowed").return { $return = 1 }'
6.2 基本语法
SystemTap脚本语法比较简单,与C语言类似,只是每一行结尾";"是可选的。主要语句如下:
if/else、while、for/foreach、break/continue、return、next、delete、try/catch
其中:
next:主要在probe探测点逻辑处理中使用,调用此语句时,立刻从调用函数中退出。不同于exit()的是,next只是退出当前的调用函数,而此SystemTap并没有终了,但exit()则会终止SystemTap。
变量:
不需要明确声明变量类型,脚本语言会根据函数参数等自动判断变量是什么类型的。
局部变量:在声明的probe和block(”{ }“范围内的部分)内有效。
全局变量:用”global“声明的变量,在此SystemTap的整个动作过程中都有效。全局变量的声明位置没有具体要求。需要注意的是,全局变量默认有锁保护,使用过多会有性能损失,如果用全局变量保存指针,可能出现指针所指的内容被进程修改,在探测点中拿不到真正的数据。
获取进程中的变量(全局变量、局部变量、参数)直接在变量名前面加$即可(后面会有例子)
注释:
# ...... : Shell语言风格
//...... : C++语言风格
/*......*/ : C语言风格
操作符:
比较运算符、算数运算符基本上与C语言一样,需要特别指出的是:
(1)、.操作符:连接两个字符串,类似于php;
(2)、=~和!~:正则匹配和正则不匹配;
函数定义:
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));
%}
//这里_generic_indent不用声明long/string, 可能是因为调用的是indent函数,_generic_indent是二次调用?