计算机 · 2023年2月11日 0

SystemTap

安装ubuntu debug symbols

内核:

Installing Ubuntu Kernel Debugging Symbols | Micromysteries

应用程序:

Debug Symbol Packages – Ubuntu Wiki

安装当前版本源码

Kernel/SourceCode – Ubuntu Wiki

SystemTap

直接通过apt安装的有问题,会报”Pass 4: compilation failed”,需要自己手动编译安装:

Is systemtap broken on 5.0.0 kernel? – Ask Ubuntu

systemtap教程:

SystemTap Beginners Guide

别人用systemtap实现的trace tcp内部拥塞控制算法的代码:

https://github.com/fastos/tcpdive/blob/master/src/congestion.stp

systemtap与bpftrace比较

https://catbro666.github.io/posts/46dd3f4b/

Systemtap文档笔记

3.1 Architecture

systemtap的工作过程:

1.将脚本里用到的类似库函数的东西替换为库函数的实现(一般位于/usr/share/systemtap/tapset/);

2.将systemtap脚本翻译为C代码,然后编译为内核模块;

3.加载内核模块,并且使能脚本中的所有probe(event+handler);

4.当脚本中指定的event触发时,执行handler;

5.systemtap脚本结束时,关闭所有probe,卸载内核模块;

3.2 SystemTap脚本

SystemTap脚本由多个probe组成,每个probe包含event和handler(或者称之为probe body),在SystemTap监测到系统发生event时,执行相应的handler。

probe格式:

probe event {statements}

每个probe可以有多个event,用逗号隔开就行。

SystemTap脚本支持定义函数,以达到重复利用代码的目的。

function function_name(arguments) {statements}
probe event {function_name(arguments)}

3.2.1 Event

Event可以分为两类,同步和异步。

Synchronous Events

同步事件在内核代码执行到指定代码(probe中的event事件)时触发。

例子:

  • syscall.system_call,可以加.return后缀,表示函数返回这个事件syscall.system_call.return
  • vfs.file_operation,也可以加.return后缀。
  • kernel.function(“function”),监听内核中任意一个函数的调用。内核函数名字function还可以使用通配符和按照函数所在源文件来指定,如下面的代码可以监听net/socket.c文件中所有函数的进入和退出:
probe kernel.function("*@net/socket.c") { },
probe kernel.function("*@net/socket.c").return { }
  • kernel.trace(“tracepoint“),用于监听内核中的tracepoint,tracepoint是一些内核在编译时已经定好了的可以hook的点。
  • module(“module“).function(“function“),按照module名字来指定要监听的函数,也可以使用通配符。
probe module("ext3").function("*") { }
probe module("ext3").function("*").return { }

Asynchronous Events

异步事件不是用于监听系统中事件的发生,而是一些特殊的时间点:

  • begin,SystemTap脚本执行的开始时刻
  • end,SystemTap脚本执行的结束时刻
  • timer events,SystemTap脚本可以设定timer,以支持一些定时任务:
probe timer.s(4)
{
  printf("hello world\n")
}

timer.s也可以替换为timer.ms,timer.us,timer.ns,timer.hz,timer.jiffies。

关于systemtap支持的event的更全面的介绍,可以通过命令查看man stapprobes。

3.2.2 SystemTap Handler/Body

handler中每一行代码末尾的分号不是必须的;

SystemTap脚本只有在执行exit()函数后才会退出,或者由用户通过Ctrl+C中断执行。

printf语句的格式化语法与C语言完全一致。

支持三种注释方法:#,//,/*…*/

SystemTap中的一些辅助函数:

  • tid(),获取当前线程的ID
  • uid(),获取当前用户的ID
  • cpu(),获取当前CPU的编号
  • gettimeofday_s(),epoch,秒级。也有更高精度的gettimeofday_ms/ns/us。
  • ctime(),与c语言中的ctime一致
  • PP(),获取一个描述当前正在执行的probe point的字符串
  • thread_indent(),用于获取一个包含调用进程名字、timestamp(从第一次调用thread_indent()开始),和缩进的字符串。缩进的长度由thread_indent()函数内部的技术器维护,每次调用thread_indent()时,加减参数值指定的长度。典型使用示例:
probe kernel.function("*@net/socket.c").call
{
  printf ("%s -> %s\n", thread_indent(1), probefunc())
}

probe kernel.function("*@net/socket.c").return
{
  printf ("%s <- %s\n", thread_indent(-1), probefunc())
}

上面的代码可以用来观察函数的进入退出执行过程,并且可以通过缩进方便的匹配到每一次函数调用的进入和退出。

  • name,获取系统调用的名字,只有在syscall.system_call事件中才可以使用,注意这不是一个函数
  • target(),与stap script -x process ID stap script -c command配合使用,表示指定的进程pid。

更全面的SystemTap内置函数需要参考命令man stapfuncs。

想提前退出probe handler,需要使用next语句(exit()是直接退出整个systemtap脚本)。

3.3 Basic SystemTab Handler Constructs

3.3.1 variables

变量类型不需要显示指定,而是通过赋值SystemTap自动进行推断;

全局变量必须在probe之外通过global关键字显示声明;

global count_jiffies, count_ms
probe timer.jiffies(100) { count_jiffies ++ }
probe timer.ms(100) { count_ms ++ }
probe timer.ms(12345)
{
  hz=(1000*count_jiffies) / count_ms
  printf ("jiffies:ms ratio %d:%d => CONFIG_HZ=%d\n",
    count_jiffies, count_ms, hz)
  exit ()
}

3.3.2 Target Variables

所谓Target Variables,就是probe对应的event触发时,想要读取的内核源码中的变量。可以通过stap -L ‘kernel.function(“vfs_read”)’这种方式来确定probe的event可以查看哪些变量的值。引用target variables时,必须要加$前缀。

还可以引用probe point之外的变量,但引用时需要加上该变量的文件路径,如:

@var(“varname@src/file.c“)

@var(“varname“, “/path/to/exe/or/lib“)

SystemTap还支持用->运算符读取变量的内部成员,无论变量是指针还是结构体,->运算符都适用,SystemTap会自行判断变量的类型。

对于基础类型的指针,SystemTap可以通过下列函数读取指针指向地址的值:

  • kernel_char(address)
  • kernel_short(address)
  • kernel_int(address)
  • kernel_long(address)
  • kernel_string(address)
  • kernel_string_n(address)

3.3.2.1 Pretty Printing Target Variables

SystemTap提供了一些常用的打印函数内部变量的operation,方便用户使用:

  • $$vars,会自动扩展成sprintf(“parm1=%x … parmN=%x var1=%x … varN=%x”, parm1, …, parmN, var1, …, varN),如果定位不到变量,会打印为=?
  • $$locals,和\$\$vars类似,但是只打印local变量
  • $$parms,和\$\$vars类似,但是只打印函数参数
  • $$return,和\$\$vars类似,但是只使用于.return类型的probe point,获取函数的返回值(可以直接用$return访问函数的返回值)。

使用示例:

stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms); exit(); }'

对于上述几个operation,还可以通过加$后缀将要打印的指针型变量转换为指针指向的对象来打印,

stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$); exit(); }'

$后缀可以加多个,加一层,表示进行几层的指针引用转换。比如如果一个指针指向一个包含了指针的结构体,那么$$vars$$,可以把这个结构体中的指针也作寻址操作,转换为具体的变量打印。

3.3.2.2 Typecasting

有时候内核源码里使用了void类型指针,但是我们也实际知道这个指针指向内存的具体类型,这时需要进行强制转换。

function task_state:long (task:long)
{
    return @cast(task, "task_struct", "kernel<linux/sched.h>")->state
}

3.3.2.3 Checking Target Variable Availability

可以通过@defined操作符测试在probe point处,变量是否存在:

probe vm.pagefault = kernel.function("__handle_mm_fault@mm/memory.c") ?,
                     kernel.function("handle_mm_fault@mm/memory.c") ?
{
        name = "pagefault"
        write_access = (@defined($flags)
			? $flags & FAULT_FLAG_WRITE : $write_access)
	address =  $address
}

3.3.3 Conditional Statements

Conditional Statements与C语言一致。

Conditional operators支持:>=,<=,!=。没有>和<?

3.3.4 Command-Line Arguments

如果脚本想要接收命令行的参数,使用$1或@1。$表示参数是整数类型,@表示参数是字符类型,数字表示是引用的命令行的第几个参数。

probe kernel.function(@1) { }
probe kernel.function(@1).return { }

3.4 Associative Arrays

Associative arrays必须用global关键字声明,即时他们只在某些probe中使用到。

关联数组允许key由最多9个表达式构成,表达式间用逗号分隔:

device[pid(),execname(),uid(),ppid(),"W"] = devname

3.5 Array Operations in SystemTap

如果关联数组中没有指定的key,那么在读取关联数组中这个key对应的value时,关联数组会返回0或空字符串(具体与关联数组的类型有关)。

关联数组支持++操作符:

array[key]++

遍历关联数组元素:

probe timer.s(3)
{
  foreach (count in reads)
    printf("%s : %d \n", count, reads[count])
}

probe timer.s(3)
{
  foreach (count in reads- limit 10)
    printf("%s : %d \n", count, reads[count])
}

减号表示以降序方式遍历,加号表示升序遍历,limit表示限制遍历的元素个数

删除关联数组中的元素

delete array[key];

清空关联数组

delete array;

membership testing

if([index_expression] in array_name) statement

SystemTap脚本中变量的统计功能

对于脚本中的变量和map中的元素,可以使用<<<操作符,表示为这个变量输入一个统计用的sample,输入的统计sample不会影响变量字面上的值,变量的统计数据需要通过下面的几个extractor获取,格式为@extractor(variable/array index expression):

  • count 返回统计到的sample元素个数
  • sum 返回统计到的sample之和
  • min 最小值
  • max 最大值
  • avg 平均值

使用统计功能时,关联数组的key只能支持最多5个表达式作索引而不是平时的9个。

3.6 Tapsets

tapsets就是SystemTap的标准库,位于/usr/share/systemtap/tapset/。