SystemTap使用指南

本文详细介绍了SystemTap的使用,从原理、安装到脚本语言的关键元素如probe、基本语法、变量、注释、操作符和函数。此外,还分享了定位函数位置、查看探测点变量、调用堆栈、获取函数参数、遍历数组和调试内存泄漏等实用技巧,是深入理解SystemTap的全面指南。
摘要由CSDN通过智能技术生成

1.简介

 SystemTap是一个Linux非常有用的调试(跟踪/探测)工具,常用于Linux
 内核或者应用程序的信息采集,比如:获取一个函数里面运行时的变
 量、调用堆栈,甚至可以直接修改变量的值,对诊断性能或功能问题非
 常有帮助。SystemTap提供非常简单的命令行接口和很简洁的脚本语
 言,以及非常丰富的tapset和例子。  

2.何时使用

定位(内核)函数位置
查看函数被调用时的调用堆栈、局部变量、参数
查看函数指针变量实际指的是哪个函数
查看代码的执行轨迹(哪些行被执行了)
查看内核或者进程的执行流程
调试内存泄露或者内存重复释放
统计函数调用次数
......

3.原理

在网上找了个原理图:

systemtap

SystemTap的处理流程有5个步骤:解析script文件(parse)、细化(elaborate)、script文件翻译成C语言代码(translate)、编译C语言代码(生成内核模块)(build)、加载内核模块(run)

systemtap_phase

4.安装

SystemTap依赖的package:
elfutils、gcc、kernel-devel、kernel-debuginfo
如果调用用户态进程,还需要该程序有调试符号,否则无法调试。
推荐使用最新稳定版的SystemTap,目前最新稳定版为:systemtap-2.9.tar.gz

5.入门

5.1 stap命令

stap [OPTIONS] FILENAME [ARGUMENTS]
stap [OPTIONS] - [ARGUMENTS]
stap [OPTIONS] –e SCRIPT [ARGUMENTS]

比较常用和有用的参数:
-e SCRIPT               Run given script.
-l PROBE                List matching probes.
-L PROBE                List matching probes and local variables.
-g                      guru mode 
-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

Hello World:

root@j9 ~/stp# cat hello-world.stp
probe begin {
    print("===Hello World===\n")
}

probe end {
    print("===GunLe===\n")
}
root@j9 ~/stp# stap hello-world.stp 
===Hello World===
^C===GunLe===
root@j9 ~/stp# stap -e 'probe begin { printf("Hello World!\n") exit() }'   
Hello World!
root@j9 ~/stp#

5.2 staprun命令

staprun [OPTIONS] MODULE [MODULE-OPTIONS]

stap命令与staprun命令的区别在于:
stap命令的操作对象是stp文件或script命令等,而staprun命令的操作对象是编译生成的内核模块。

6.脚本语言

6.1 probe

“probe” <=> “探测”, 是SystemTap进行具体地收集数据的关键字。
systemtap_probe

“probe point” 是probe动作的时机,也称探测点。也就是probe程序监视的某事件点,一旦侦测的事件触发了,则probe将从此处插入内核或者用户进程中。
“probe handle” 是当probe插入内核或者用户进程后所做的具体动作。

probe用法:

probe probe-point { statement }

在Hello World例子中begin和end就是probe-point, statement就是该探测点的处理逻辑,在Hello World例子中statement只有一行print,statement可以是复杂的代码块。
探测点语法:

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)

PATTERN语法为:

func[@file]
func@file:linenumber

例如:

kernel.function("*init*")
module("ext3").function("*")
kernel.statement("*@kernel/time.c:296")
process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request")

在return探测点可以用&dollar;return获取该函数的返回值。
inline函数无法安装.return探测点,也无法用$return获取其返回值。

6.2 基本语法

SystemTap脚本语法比较简单,与C语言类似,只是每一行结尾";"是可选的。主要语句如下:
if/else、while、for/foreach、break/continue、return、next、delete、try/catch
其中:
next:主要在probe探测点逻辑处理中使用,调用此语句时,立刻从调用函数中退出。不同于exit()的是,next只是退出当前的调用函数,而此SystemTap并没有终了,但exit()则会终止SystemTap。

6.2.1 变量

不需要明确声明变量类型,脚本语言会根据函数参数等自动判断变量是什么类型的。
局部变量:在声明的probe和block(”{ }“范围内的部分)内有效。
全局变量:用”global“声明的变量,在此SystemTap的整个动作过程中都有效。全局变量的声明位置没有具体要求。需要注意的是,全局变量默认有锁保护,使用过多会有性能损失,如果用全局变量保存指针,可能出现指针所指的内容被进程修改,在探测点中拿不到真正的数据。
获取进程中的变量(全局变量、局部变量、参数)直接在变量名前面加$即可(后面会有例子)

6.2.2 注释

# ...... : Shell语言风格    
//...... : C++语言风格    
 /*......*/ : C语言风格

6.2.3 操作符

比较运算符、算数运算符基本上与C语言一样,需要特别指出的是:
(1)、.操作符:连接两个字符串,类似于php;
(2)、=~和!~:正则匹配和正则不匹配;

6.2.4 函数

函数定义例子:

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));
%}

官方有很多很有用的函数,详情请参考:https://sourceware.org/systemtap/tapsets/
以及在本机安装了SystemTap之后在目录/usr/local/share/systemtap/tapset/下也可以看具体函数的实现以及一些奇特的用法。

7.技巧

7.1 定位函数位置

在一个大型项目中找出函数在哪里定义有时很有用,特别是一些比较难找出在哪里定义的函数,比如内核或者glibc中的某个函数想要看其实现时,首先得找出其在哪个文件的哪一行定义,用SystemTap一行命令就可以搞定。
比如要看printf在glibc中哪里定义的:

root@j9 ~# stap -l 'process("/lib/x86_64-linux-gnu/libc.so.6").function("printf")' 
process("/lib/x86_64-linux-gnu/libc-2.15.so").function("__printf@/build/buildd/eglibc-2.15/stdio-common/printf.c:29")

可以看出printf是在printf.c第29行定义的。
再比如要看内核中recv系统的调用是在哪里定义的:

root@j9 ~# stap -l 'kernel.function("sys_recv")'
kernel.function("sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/socket.c:1868")

可以看出recv是在socket.c第1868行定义的。
甚至可以*号来模糊查找:

root@j9 ~# stap -l 'kernel.function("*recv")'   
kernel.function("__audit_mq_sendrecv@/build/buildd/linux-lts-trusty-3.13.0/kernel/auditsc.c:2062")
kernel.function("audit_mq_sendrecv@/build/buildd/linux-lts-trusty-3.13.0/include/linux/audit.h:263")
kernel.function("compat_sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/compat.c:762")
kernel.function("i2c_master_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/i2c/i2c-core.c:1827")
kernel.function("ip_cmsg_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/ip_sockglue.c:147")
kernel.function("kgdb_tty_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/tty/serial/kgdb_nmi.c:109")
kernel.function("ppp_do_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/net/ppp/ppp_generic.c:1617")
kernel.function("scm_recv@/build/buildd/linux-lts-trusty-3.13.0/include/net/scm.h:109")
kernel.function("sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/socket.c:1868")
kernel.function("tcp_event_data_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/tcp_input.c:615")
kernel.function("tcp_splice_data_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/tcp.c:637")
kernel.function("tpm_tis_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/char/tpm/tpm_tis.c:231")
kernel.function("try_fill_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/net/virtio_net.c:615")

同理,也可以用来定位用户进程的函数位置:
比如tengine的文件ngx_shmem.c里面为了兼容各个操作系统而实现了三个版本的ngx_shm_alloc,用#if (NGX_HAVE_MAP_ANON)、#elif (NGX_HAVE_MAP_DEVZERO)、#elif (NGX_HAVE_SYSVSHM)、#endif来做条件编译,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值