SystemTap工具的使用基础

systemtap工具的安装

准备工作

uname -a

查看当前内核版本是哪一个,然后使用

yum install kernel-devel

安装kernel debuginfo包

rpm -qi kernel-devel

找到内核构建的详细信息,然后去对应发布网站上找kernel-debuginfo和kernel-debuginfo-common包。

安装systemtap包

yum install systemtap

测试

完成安装后可以通过下面命令测试systemtap

stap -ve ‘probe begin { print(“hello world\n”) exit()}’

进行测试,看看systemtap有无安装成功。

systemtap常规命令

查看内核某个函数可以查看的target变量

下面命令演示查看__lookup_hash()函数返回时刻可以查看到的变量

stap -ve ‘probe kernel.function(“__lookup_hash”).return’ //查看__lookup_hash()函数返回时刻可以systemtap工具可以查看的target变量。
kernel.function(“__lookup_hash@fs/namei.c:1383”).return $return:struct dentry* $name:struct qstr* $base:struct dentry* $flags:unsigned int $need_lookup:bool

在上表中显示了lookup_hash在文件中的行号,显示了名为$return 的变量,其实这个return变量就是systemtap表示函数返回值的。而$name,$base,$flag我们对着linux源码看发现这是__lookup_hash的三个入参。
下面命令可以查看__lookup_hash函数入口可以查看的变量

stap -L ‘kernel.function(__lookup_hash)’

也可以通过statement方式查看内核符号表里有的__lookup_hash相关的行

stap -L ‘kernel.statement(“*”)’ |grep __lookup_hash

如果查找的内核函数位于某个模块里可以使用下面命令:

stap -L ‘module(“overlay”).function(“*”)’|grep ovl_lookup
stap -L ‘module(“overlay”).statement(“*”)’|grep ovl_lookup

查看用户态进程的可以stap的函数

通过下面命令可以查看到某个正在运行的进程的函数

stap -L ‘process(“/usr/bin/dockerd”).function(“*”)’|grep -i “syscall.Mount”
process(“/usr/bin/dockerd”).function(“syscall.Mount@/usr/local/go/syscall/syscall_linux.go:796”) $datap:uint8*
$source:struct string $target:struct string flags:uintptr err:error

上例中看到找到了syscall.Mount函数,并且把它的所有参数和参数类型都打印了出来。
后面可以在stap脚本中,这个函数的上下文里直接使用这些参数,例如通过$source可以访问到参数source

使用systemtap打印目标函数的变量

systemtap支持print()和printf()函数,其中printf使用语法和c语言一致。支持%s,%d,%x格式

常用变量

内部变量名 功能 其他
$$locals 探测点上所有的本地变量(含参数) n/a
$$parms 探测点上函数的参数 n/a
$$returns 探测点上所有的返回值 只对return probe生效

变量的显示方式

在systemtap里凡是以$开头的变量都是目标变量,如果目标变量结构体指针或者结构体对象,那么可以使用->直接访问其成员。例如上例中:

$return->d_inode //就是__lookup_hash()返回值dentry* 的d_inode成员的值。

常规情况下,printf()打印target变量时刻,只打印其值。如果需要将其成员(指针类型的target需要将其指向的对象的成员展开)可以在target变量后面加$的方式例如:

$return$ //显示返回值指向的dentry所有成员。

一般情况下对struct的展开只会到成员值一级,如果相对成员内部继续展开可以在目标变量后面跟$$

if逻辑语句

在systemtap中支持逻辑if语句格式为:

if (expr) {
语句
}

逻辑语句支持以下比较
==,!=,>=,>,<,<=

systemtap的内置函数

用户空间下取得目标内的值

函数 含义 备注
User_char(address) 取得地址上的字符串值 Na
User_short(address) 取得short值 na
user_init(address) 取得int值 na
User_long(address) 取得long na
user_string(address) 取得string na
user_string_n(address) 取得string,最长n个byte na

用户空间下堆栈打印

stap -d /bin/ls --ldd -e ‘probe process(“ls”).function(“xmalloc”) {print_usyms(ubackstrace())}’ -c “ls -l”

上述例子对ls -l下的xmalloc进行堆栈回溯:
-d 可执行文件名
--ldd 指明共享库
-c “ls -l” 执行的子进程体

例子

内核态例子

例子1

下面例子将打印__lookup_hash中return返回dentry*里inode指向的i_ino子成员

stap -ve ‘probe kernel.function(“__lookup_hash”).return { if ($return!=0) { if($return!=-2) { if ($return->d_inode!=0){
printf(“return dentry:%x,dentry->d_inode->i_ino:%x”,$return,$return->d_inode->i_ino)}}}}’ -o zxy.txt

这一例子中-o zxy.txt的意思就是将结果写入文件zxy.txt中(默认输出到控制台)

例子2

下面例子将在内核中使用强制类型转换

stap -ve ‘probe module(“overlay”).statement(“ovl_lookup@fs/overlay/super.c:603”){ print_syms(backtrace()) if($dentry->d_inode)’ printf(“overlay_lookup 603: inode :%p,ino:%d \n”,@cast(@cast($dentry,”dentry”,”kernel”)->d_inode,”inode”,”kernel”)->i_ino)

这里解释一下,内核中方法强制转换

@cast(p,”type_name”[,”module”])->member

内核态小技巧

在用systemtap跟踪内核时使用堆栈打印命令,常常打印不出来另外模块的函数,这是因为这些模块没有被加载。可以在systemtap启动命令使用--all-modules 方法强制将所有模块符号加载起来。

用户态例子

下面例子对用golang写的dockerd进程syscall.Mount调用入口时刻打印syscall.Mount()函数的参数
source的string字段内容

probe process(“/usr/bin/dockerd”).function(“syscall.Mount”).call{
if($source->str)
printf(“source is %s Len %d \n”,user_string($source->str),$source->len)
print_usyms(ubacktrace())
}

下面例子打印golang写的dockerd进程xxx.Get函数返回时刻的参数情况

probe process(“/usr/bin/dockerd”).function(“github.com/docker/docker/daemon/graphdriver/overlay2.(*Driver).Get”).return{
printf(“return Parma :%s\n”,$$parms$)

}

使用systemtap抓golang

systemtap对golang支持不够完美,用户需要自己解析基本结构例如golang的string,array和slice这些都需要用户自己解析。string被systemtap识别为struct string,此结构systemtap可以识别的定义可以简化为:

Struct string{
int len
char* str
}

需要注意的是通过systemtap打印golang string的string->str会多打很多字符,因为string成员str并非按照c语言定义的字符串以\0表示字符串结束,我们只能结合string的字段len来获取精确的字符串内容
slice完全不被systemtap识别,我们可以将systemtap可以识别的slice简化为此种定义:

struct slice{
array
len
cap
}

其中array就是指向slice存储单元的首地址。
要是我们想获取helo=[]string{“hello”,”world”}这样的字符串slice的内容可以通过systemtap提供的@cast(addr,”type”,”file”)函数将某个地址强转为file中定义的type结构。具体来说可以如下做获取hello的内容

for(i=0;i<$helo->len;i++)
printf(“slice[%d]:%s\n”,i,user_string((&@cast($helo->array,”struct string”)[i])->str))

probe process(“/bin/docker-runc”).function(“github.com/opencontainers/runc/libcontainer/specconv.createDevices”).call{
          for(i=0;i<$spec->Linux->Device->len;i++){
             printf(“%d:Major %d n”,i,user_int((&@cast($spec->Linux->Devices->array,”github.com/opencontainers/runtime-spec/specs-go.Device”)[i])->Major))
         }
}

你可能感兴趣的:(SystemTap工具的使用基础)