专注于快乐的事情

systemtap学习总结

systemtap简介

systemtap安装测试

安装

安装elfutils,提供分析调试信息的库函数,及libcap-dev

sudo apt-get install elfutils

sudo apt-get install libcap-dev

sudo apt-get install systemtap

测试

stap-prep

出现如下错误
You need package linux-image-4.4.0-47-generic-dbgsym but it does not seem to be available

由于发行版的内核默认无内核调试信息,所以需要一个调试内核镜像

wget http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-4.4.0-47-generic-dbgsym_4.4.0-47.68_amd64.ddeb

dpkg -i linux-image-4.4.0-47-generic-dbgsym_4.4.0-47.68_amd64.ddeb

查询内核版本的方法
cat /proc/version_signature
或者uname -r

执行测试

stap -ve 'probe begin { log("hello world") exit() }'

没有出现错误,表示成功.
-v 打印编译阶段的详细信息

systemtap原理

SystemTap采用其他的内核框架做源:静态指针使用tracepoints,动态指针使用Kprobe。用户级别的使用uprobes。

Kprobes 从 2.6.9 版本开始就添加到主流的 Linux 内核中。它提供一些不同的服务,但最重要的两种服务是 Kprobe 和 Kretprobe。

Kprobe 特定于架构,它在需要检查的指令的第一个字节中插入一个断点指令。当调用该指令时,将执行针对探针的特定处理函数。执行完成之后,接着执行原始的指令(从断点开始)。

断点指令(breakpoint instruction):__asm INT 3,机器码为CC。
断点中断(INT3)是一种软中断,当执行到INT 3指令时,CPU会把当时的程序指针(CS和EIP)压入堆栈保存起来,
然后通过中断向量表调用INT 3所对应的中断例程。
INT是软中断指令,中断向量表是中断号和中断处理函数地址的对应表。
INT 3即触发软中断3,相应的中断处理函数的地址为:中断向量表地址 + 4 * 3。

Kretprobes 有所不同,它操作调用函数的返回结果。注意,因为一个函数可能有多个返回点,所以听起来事情有些复杂。不过,它实际使用一种称为 trampoline 的简单技术。您将向函数条目添加一小段代码,而不是检查函数中的每个返回点。这段代码使用 trampoline 地址替换堆栈上的返回地址 (Kretprobe 地址)。当该函数存在时,它没有返回到调用方,而是调用 Kretprobe(执行它的功能),然后从 Kretprobe 返回到实际的调用方。

stap 实用程序将 stap 脚本转换成提供探针行为的内核模块。

SystemTap 脚本

查看一个脚本,nethist.stp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
global histogram

probe begin {
printf("Capturing...\n")
}

probe netdev.receive {
histogram <<< length
}

probe netdev.transmit {
histogram <<< length
}

probe end {
printf( "\n" )
print( @hist_log(histogram) )
}

运行
stap nethist.stp

用probe指定一个探测点(probe-point)(探针),以及在这个探测点处执行的处理函数

常用探针类型

探针类型 说明
begin 在脚本开始时触发
end 在脚本结束时触发
kernel.function(“sys_sync”) 调用 sys_sync 时触发
kernel.function(“sys_sync”).call 同上
kernel.function(“sys_sync”).return 返回 sys_sync 时触发
kernel.syscall.* 进行任何系统调用时触发
kernel.function(“*@kernel/fork.c:934”) 到达 fork.c 的第 934 行时触发
module(“ext3”).function(“ext3_file_write”) 调用 ext3 write 函数时触发
timer.jiffies(1000) 每隔 1000 个内核 jiffy 触发一次
timer.ms(200).randomize(50) 每隔 200 毫秒触发一次,带有线性分布的随机附加时间(-50 到 +50)

探测点的一般语法形式
kernel.function(PATTERN)
kernel.function(PATTERN).call
kernel.function(PATTERN).return

缺少后缀时意味着探测函数的进入点,默认为call。

.function 使探针定位在命名函数的开始之处,因此探针可用上下文变量的方式来获取函数参数。
.return 让探针定位到命名函数返回的那一时刻
.call 调用时触发,默认。
.statement,使探针探测到确切的代码行
例如:
kernel.statement("bio_init@fs/bio.c+3")//引用文件fs/bio.c 内bio_init+3 这一行语句

PATTERN 是一个字符串字面值,它标识程序中的代码点。它由3 部分构成。

  1. 第一部分是函数名字,可使用星号和问号通配符来匹配多个函数名字。
  2. 第二部分是可选的,它以@ 字符开头,紧跟着此函数所在源文件的路径。默认为为从Linux 代码树顶层目录开始的相对路径
  3. 如果给定文件名,第三部仍是可选的。它以“: ”或“+ ”开头,用来标识源文件的行号。

    ”:” 后面跟的是绝对行号
    ”+” 后面跟的是函数入口的相对行号
    ”:*” 匹配函数的每一行
    ”:x-y” 可以从x 行匹配到y行

内置变量

column column
execname()
uid

##内置函数

名称 说明
cpu() 执行当前进程的CPU number
execname() 返回当前执行的进程名
tid
pid() 当前进程的ID
uid() 当前进程的user id
stp_pid()
pp()
thread_indent()
probefunc() 返回被探测的函数名
probemod()
print_backtrace() 打印进程堆栈回朔方法
strlen()
substr()
isinstr()
strtol()
get_cycles()
gettimeofday_s()
gettimeofday_ns() 当前时间,自启动以来的纳秒数
target()
task_current() 指向当前线程内核结构的指针

语法

##基本格式

变量属于弱数据类型,不用事先声明,不用指定数据类型。
probe-handler中定义的变量是局部的,不能在其它探测点处理函数中使用。
global符号用于定义全局变量。

String连接符是“.”,比较符为“==”。
next语句:执行到next语句时,会马上从探测点处理函数中返回。

变量的引用

$varname // 引用变量varname
$var->field // 引用结构的成员变量
$var[N] // 引用数组的元素
&$var // 变量的地址
另外的风格
@var(“varname”) // 引用变量varname
@var(“var@src/file.c”) // 引用src/file.c在被编译时的全局变量varname
@var(“varname@file.c“)->field // 引用结构的成员变量
@var(“var@file.c“)[N] // 引用数组的元素
&@var(“var@file.c“) // 变量的地址

关联数组

关联数组是用哈希表实现的,最大大小在一开始就设定了。
关联数组必须是全局的,不能在探测点处理函数内部定义。
数组的索引最多可以有9个,用逗号隔开,可以是数字或字符串。
元素的数据类型有三种:数值、字符串、统计类型。
例如:
foo[4, “hello”]++

统计类型

操作符“<<<”
例如:g_value <<< b # 相当于C语言的g_value += b

@count(g_value):所有统计操作的操作次数
@sum(g_value):所有统计操作的操作数的总和
@min(g_value):所有统计操作的操作数的最小值
@max(g_value):所有统计操作的操作数的最大值
@avg(g_value):所有统计操作的操作数的平均值

SystemTap其他用法

获取stap命令行参数

假定该脚本的名字为example.stp
probe begin { printf(“%d, %s/n”, $1, @2) }
运行如下:
stap example.stp 10 mystring
那么,$1 会被替换成10 ,而@2 会被替换成”mystring” ,结果输出:
10, mystring

$1 … $ 将参数转换成整数
@1 … @ 将参数转换成字符串

查找匹配的内核函数和变量

查找所有的系统调用
stap -l 'syscall.*'
stap -L syscall.read.return

open系统调用在内核的实现
stap -l 'kernel.function("sys_open")'
kernel.function("SyS_open@/build/linux-xHzv4a/linux-4.4.0/fs/open.c:1038")

查找名字中包含nit的内核函数:
stap -l 'kernel.function("*nit*")'

查看执行到这个探测点时,哪些上下文变量是可用的
stap -L 'kernel.function("vfs_write")'
stap -L 'netdev.transmit'

官方提供的例子参考
https://sourceware.org/systemtap/wiki/WarStories

参考

评论系统未开启,无法评论!