SystemTap
SystemTap 是一个 Linux 非常有用的调试工具,提供了对用户级和内核级的静态和动态追踪功能,比如可以获取一个函数里面运行时的变量、调用堆栈,甚至可以直接修改变量的值,对诊断性能或功能问题非常有帮助。SystemTap 采用其他内核框架做数据源,静态探针用 tracepoints、动态探针用 kprobes、用户级探针用 uprobe。SystemTap 提供非常简单的命令行接口和很简洁的脚本语言,以及非常丰富的 tapset 和例子。本文将介绍其原理和使用方法,文中所有代码可以在 Github 中找到。
工作原理
Systemtap 工作原理如下图所示,主要分为以下几步:
- 开发者根据
systemtap语法编写stp脚本语言probe.stp - 解析
stp脚本语言代码,主要是词法分析和语法分析 - 解读
stp代码,语义分析 - 翻译成 C 语言,也即是生成中间代码
probe.c - 将
probe.c编译成内核模块 - 加载内核模块之后,将所有探测的事件以钩子的方式挂到内核上,当任何处理器上的某个事件发生时,相应钩子上句柄就会被执行
- 当 systemtap 会话结束之后,钩子从内核上取下,移除模块
下面是一个简单的 hello world 样例,通过 stap hello.stp 命令,便会执行以上所有过程。
|
|
语法规则
systemtap 的核心思想是定义一个事件(event),以及给出处理该事件的句柄(Handler)。当一个特定的事件发生时,内核运行该处理句柄,就像快速调用一个子函数一样,处理完之后恢复到内核原始状态。这里有两个概念:
- Event:systemtap 定义了很多种事件,例如进入或退出某个内核函数、定时器时间到、整个 systemtap 会话启动或退出等等。
- Handler:就是一些脚本语句,描述了当事件发生时要完成的工作,通常是从事件的上下文提取数据,将它们存入内部变量中,或者打印出来。
probe point
probe是 SystemTap 进行具体地收集数据的关键字probe point是 probe 动作的时机,是 probe 程序监视的某事件点,一旦侦测的事件触发了,则probe将从此处插入内核或者用户进程中probe handle是当 probe 插入内核或者用户进程后所做的具体动作
probe 用法,在 Hello World 例子中 begin和 end 就是 probe-point, statement就是该探测点的处理逻辑,在 Hello World 例子中 statement 只有一行 print,statement 可以是复杂的代码块。
|
|
探测点语法:
|
|
PATTERN 语法为:
|
|
例如:
|
|
在 return 探测点可以用 $return 获取该函数的返回值,inline函数无法安装 .return 探测点,也无法用 $return 获取其返回值。
Systemtap 支持许多内置探测点,这些事件是 systemtap 官方预先写好的脚本,被称为 tapset。可以参考官方的tapsets 手册使用这些库函数,在安装完成后,一般在本地位置是 /usr/share/systemtap/tapset,如果想引用其他路径下的 stap 脚本,需要添加参数 -I。
常用的探测点有:
|
|
另外还封装了一些常用的可打印值,例如:
|
|
下面演示了通过 systemtap 探测一个用户程序的例子:
|
|
使用下面这个 systemtap 脚本针对代码中的第 8 行和第 10 行打印当时变量 a 的值:
|
|
输出如下:
$ sudo stap cc_stap_test.stp -c ./a.out
a:1
a:2
systemtap probe line 8 a:1
systemtap probe line 10 a:2
基本语法
脚本命名
脚本名字符合 linux 文件命名即可。一般名字后辍使用 .stp,方便人们知道它是 systemtap 脚本,比如 memory.stp
注释
脚本支持多种注释方式,# 、//、/**/ 均可。可依据个人习惯使用。另外类似其他脚本,systemtap 脚本在开头也需要标明脚本解析器的路径,一般是 #!/usr/bin/stap,不确定的可以通过命令 whereis stap找到脚本解析器位置;
变量
变量需要字母开头,一般由字母、数字组成,当然还可以包括美元符号和下划线字符。变量可以在函数的任意处声明,也可以直接使用(通过第一次使用探测变量类型)。变量默认作用域是函数或括号内部,定义全局变量需要加 global(写在函数外任意处)。
|
|
数组
数组必须被定义成 global 变量,默认大小不超过 2048(MAXMAPENTRIES),定义时可以省略大小,除非是想定义超过 2048 的大数组:
|
|
另外还支持关联数组,关联数组中的索引或键由一个或多个字符串或整数值(逗号隔开)组成:
|
|
条件语句
用法和 C 语言一样:
|
|
循环
基础用法和 C 语言一样,比如:
|
|
除此之外,还提供一种用于数组的特殊循环 foreach:
|
|
函数
普通函数使用 function声明,函数返回值类型通过 : 跟在函数名后面;参数类型通过 : 跟在函数参数后面,多个参数通过",“隔开,例如:
|
|
另外一种函数是 probe 函数,下面以探测内核函数和模块函数为例,介绍几种常见用法:
- probe 内核和模块函数通用格式:
|
|
- 函数名支持通配符,例如:
|
|
- 对于使用相同
handle函数的 probe 函数,可以叠加定义:
|
|
- 有时候因为内核版本不同,有些函数名字不一样,或者某些版本里函数不存在,systemtap 提供了几种
条件函数和可选择函数供灵活使用:
|
|
- 还可以在函数末尾加上
.call或.return,分别表示函数被调用和返回时 probe:
|
|
通过命令行传递参数
和 shell 等脚本类似,可以在脚本里引用命令行传递的参数。不过 stp 脚本需要预先知道参数的类型,因为引用不同类型参数方式不同。
- 对于整数类型参数,通过“$N”引用,N 是第几个参数(base 1);
- 对于字符串参数,通过“@N”引用,N 是第几个参数(base 1),如果字符串中间有空格,需要在字符串两边加上双引号(不加就是两个变量);
举例:
|
|
实战演示
安装了 systemtap 之后,可以看到许多的用例脚本,主要包括 network、io、interrupt、locks、memory、process、vistualization 几个方面。
下面简单演示一个监控所有进程的收发包情况示例:
|
|
如果运行上述脚本出现以下错误,显示 semantic error: while resolving probe point: identifier 'kernel' 错误,表明是系统没有符号信息,我们需要手动的安装符号 rpm,我们可以上 http://debuginfo.centos.org 查找
- kernel-debuginfo-common-
uname -r - kernel-debuginfo-
uname -r
下载好,使用 rpm 命令安装即可:
|
|
解决上述问题之后,可以看到显示以下信息:
|
|
参考资料
-
No backlinks found.