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是二次调用?