SystemTap 使用技巧之一

2017-08-21 18:29:59 +08:00
 sherryxueli

1.简介

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

2.何时使用

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

3.原理

在网上找了个原理图:

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

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 进行具体地收集数据的关键字。

“ 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 探测点可以用$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/下也可以看具体函数的实现以及一些奇特的用法。

本文是阿里云 CDN 安防专家金九所写,下次会发布 SystemTap 使用技巧之二,欢迎沟通交流~

3658 次点击
所在节点    推广
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/384642

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX