安装与配置

在ubuntu下直接用apt-get install之后不能正常使用,提示缺少调试信息或者编译探测代码时有问题。

1. 采用官网上的解决方法

2. 可以自己重新编译一次内核,然后再手工编译一次systemtap。这样就可以正常使用了。

Systemtap的编译说明,除了下载地址并没有说太多东西。选择一个版本,自己选择了最新的2.7.

下载后解压,执行

./configure

一般来说会提示缺少组件。Systemtap最先应该是redhat开发的,所以需要的包名称ubuntu不能直接用来apt-get

列出几个自己碰到的依赖问题:

configure: error: missing gnu /usr/bin/msgfmt

apt-get install gettext

configure: error: missing elfutils development headers/libraries (install elfutils-devel, libebl-dev, libdw-dev and/or libebl-devel)

可以通过apt-get install libdw-dev解决

configure: error: in `/root/systemtap-2.7':
configure: error: C++ preprocessor "/lib/cpp" fails sanity check

安装apt-get install g++

使用用例

基本使用

详见systemtap 官方tutorial。这里做个笔记。

hello world

把systemtap脚本转换编译为内核模块然后执行预定义的动作,定义的动作由一系列的事件触发。用户可以指定在哪些事件上触发哪些指定的动作。下面是一个systemtap的helloworld,在模块装载即在脚本运行前执行一次

root@userver:~# stap hello-world.stp
hello world
root@userver:~# cat hello-world.stp
probe begin
{
print ("hello world\n")
exit ()
}

如果打开-v选项的话,可以看到执行的详细步骤:

root@userver:~# stap -v hello-world.stp
Pass : parsed user script and library script(s) using 66544virt/37432res/4324shr/33908data kb, in 120usr/10sys/127real ms.
Pass : analyzed script: probe(s), function(s), embed(s), global(s) using 67204virt/38136res/4512shr/34568data kb, in 0usr/0sys/4real ms.
Pass : translated to C into "/tmp/stapyZxhXI/stap_847497c1de7927412685a2282f37c57d_881_src.c" using 67204virt/39028res/5232shr/34568data kb, in 0usr/0sys/0real ms.
Pass : compiled C into "stap_847497c1de7927412685a2282f37c57d_881.ko" in 1000usr/590sys/1582real ms.
Pass : starting run.
helloworld
Pass : run completed in 10usr/20sys/472real ms.

如果多次运行同一个脚本的话快很多,因为systemtap直接使用了已经编译好的缓存模块文件。

还可以定时运行一定时间:

root@userver:~# stap strace-open.stp
cat() open ("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
cat() open ("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
cat() open ("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
cat() open ("iw.c", O_RDONLY)
root@userver:~# cat strace-open.stp
probe syscall.open
{
printf("%s(%d) open (%s)\n", execname(), pid(), argstr);
}
probe timer.ms() # after seconds
{
exit()
}

在systemtap运行期间执行了一个cat命令得到的结果,脚本记录了执行系统调用open的进程信息。

如何跟踪

跟踪点选择

begin The startup of the systemtap session.
end The end of the systemtap session.
kernel.function("sys_open") The entry to the function named sys_open in the kernel.
syscall.close.return The return from the close system call.
module("ext3").statement(0xdeadbeef) The addressed instruction in the ext3 filesystem driver.
timer.ms(200) A timer that fires every 200 milliseconds.
timer.profile A timer that fires periodically on every CPU.
perf.hw.cache_misses A particular number of CPU cache misses have occurred.
procfs("status").read A process trying to read a synthetic file.
process("a.out").statement("*@main.c:200") Line 200 of the a.out program.

全局:probe begin {}, probe end {}用于整个跟踪过程的开头和结尾。

函数: kernel.function("sys_open"){}用于在某个指定的内核函数中执行定义的动作,sys_open可以换成其他的函数如ext4_release_file(在文件close时会执行)

系统调用:syscall.close在执行close调用时执行,其他系统调用也是类似。因为系统调用的函数是通过宏定义实现的

修饰:

内联:kernel.function("xx").inline {} 指定函数被内联时进入

调用:kernel.function("xx").call {} 指定函数被调用是进入(不含内联)

返回:kernel.function("").return{}可以在该函数返回时执行。

格式输出

printf %s表示字符串,%d表示数值类型

tid() The id of the current thread.
pid() The process (task group) id of the current thread.
uid() The id of the current user.
execname() The name of the current process.
cpu() The current cpu number.
gettimeofday_s() Number of seconds since epoch.
get_cycles() Snapshot of hardware cycle counter.
pp() A string describing the probe point being currently handled.
ppfunc() If known, the the function name in which this probe was placed.
$$vars If available, a pretty-printed listing of all local variables in scope.
print_backtrace() If possible, print a kernel backtrace.
print_ubacktrace() If possible, print a user-space backtrace.

1. print_backtrace比较实用可以打印内核的调用栈

2. gettimeofday_s用于获得以秒为单位的时间,gettimeofday_ms则是以毫秒为单位的时间,gettimeofday_us

3. thread_indent用于进程/线程输出时的缩进相当于一个thread_local变量,参数表示作用在该变量上的一个增量,进入一个函数时参数为正值,退出时为负值,就可以产生函数调用的缩进效果,下面是一个类似tutorial上的示例:

probe kernel.function("*@fs/open.c").call {
printf("%s -> %s(%s)\n", thread_indent(4), ppfunc(), $$parms);
} probe kernel.function("*@fs/open.c").return {
printf("%s <- %s\n", thread_indent(-4), ppfunc());
}

部分输出:

 prltoolsd():    -> SyS_open(filename=0x40fa78 flags=0x0 mode=0x6118a0)
prltoolsd(): -> do_sys_open(dfd=0xffffffffffffff9c filename=0x40fa78 flags=0x8000 mode=0x18a0)
prltoolsd(): -> finish_open(file=0xffff88002ff1a000 dentry=0xffff88009a978840 open=0x0 opened=0xffff88002f89fdec)
prltoolsd(): -> do_dentry_open(f=0xffff88002ff1a000 open=0x0 cred=0xffff880140efe300)
prltoolsd(): -> generic_file_open(inode=0xffff88002f71a820 filp=0xffff88002ff1a000)
prltoolsd(): <- generic_file_open
prltoolsd(): <- do_dentry_open
prltoolsd(): <- finish_open
prltoolsd(): -> open_check_o_direct(f=0xffff88002ff1a000)
prltoolsd(): <- open_check_o_direct
prltoolsd(): <- do_sys_open
prltoolsd(): <- SyS_open
prltoolsd(): -> SyS_close(fd=0x5)
prltoolsd(): -> filp_close(filp=0xffff88002ff1a000 id=0xffff880148cb5c80)
prltoolsd(): <- filp_close
prltoolsd(): <- SyS_close

更实用的例子

分析执行

变量默认的都是局部变量,即每个处理函数内的变量是不共享的。使用全局变量的话,要在开始使用global关键字进行定义。变量是弱类型的,可以相互转换但是要手工显式进行。字符串使用.连接和php与perl一样。流程控制语句和C语言基本一致。下面是tutorial中的一个例子:

global count_jiffies, count_ms;

probe timer.jiffies() {
count_jiffies++;
} probe timer.ms() {
count_ms++;
} probe timer.ms() {
hz = ( * count_jiffies) / count_ms;
printf("jiffies:ms ratio: %d:%d = %d\n", count_jiffies, count_ms, hz);
}

目标变量

这些变量在跟踪点处理函数所在的上下文种获取,可以直接使用被跟踪函数的参数变量等。下面是一个示例:

probe kernel.function("filp_close") {
printf("%s %d: %s(%s:%d)\n",
execname(),
pid(),
ppfunc(),
kernel_string($filp->f_path->dentry->d_iname),
$filp->f_path->dentry->d_inode->i_ino);
}

输出如下:

bash : filp_close(:)
bash : filp_close(:)
bash : filp_close(:)
bash : filp_close(:)
a.out : filp_close(:)
a.out : filp_close(ld.so.cache:)
a.out : filp_close(libc-2.19.so:)
a.out : filp_close(data.out:)
a.out : filp_close(:)
a.out : filp_close(:)
a.out : filp_close(:)

函数

函数定义function name(arg1, arg2) { return somthing},跟javascript里差不多。

数组

systemtap里的数组实际上就是一个hashmap,还支持多维hash(hashmap[key1, key2...] = value),但是需要预先定义容量,当已有的元素超过容量时会报错:

global hashmap[]
global multimap[] global countmap[] probe begin {
hashmap[] = "a";
hashmap[] = "c";
hashmap[] = "last"; #
# ERROR: Array overflow, check size limit () near identifier 'hashmap' at array-demo.stp::
# hashmap[] = "excced."
# multimap[,"init"] = "important"
multimap[, "swap"] = "more import" for (i = ; i<; i++) {
countmap[i] = i * ;
}
} probe timer.ms() {
exit();
} probe end { printf("-----------------------------\n")
printf("exist: %s, %s, %s\n", hashmap[], hashmap[], hashmap[]);
printf("!exist: %s\n", hashmap[]);
printf("-----------------------------\n")
printf("exist: %s\n", multimap[, "init"]);
printf("!exist: %s\n", multimap[, "haha"]);
printf("--------------sorted by key inc[default]-------------\n")
foreach([a] in countmap) {
printf("countmap[%d] = %d\n", a, countmap[a]);
}
printf("--------------sorted by key desc-------------\n")
foreach([a-] in countmap) {
printf("countmap[%d] = %d\n", a, countmap[a]);
}
printf("--------------sorted by value desc-------------\n")
foreach([a] in countmap-) {
printf("countmap[%d] = %d\n", a, countmap[a]);
} }

foreach 语法默认对hashmap中的key进行一个升序的迭代,如果要改变方向可以在key后加个减号,如果需要按值升降序迭代则在hashmap数组名称后加符号。单个key时foreach中的[]可以省略。

统计聚合

聚合变量操作可以使用<<<对变量进行增量,按照tutorial的解释这个变量是分布在各个CPU特有的关联空间所以可以减少竞争,然后使用@avg(增量值的平均),@sum(增量值的累加),@count(增量执行次数)函数进行聚合,不能直接访问。

global hitcount[];

probe kernel.function("__schedule") {
hitcount[execname()] <<< ;
} probe timer.ms() {
exit();
} probe end {
foreach (prog in hitcount) {
printf("%15s : %-6d\n", prog, @count(hitcount[prog]));
}
}

运行结果:

root@userver:~/stp# stap schedule-stat.stp
swapper/ :
rs:main Q:Reg :
rcu_sched :
kworker/:1H :
kworker/: :
watchdog/ :
rcuos/ :
kworker/: :
systemd-udevd :
swapper/ :
rcuos/ :
kworker/u64: :
jbd2/sda1- :
migration/ :
khugepaged :
prltoolsd :
watchdog/ :
in:imklog :
stapio :
ksoftirqd/ :

每次执行都是+1的话不能体现出这些聚集函数的作用,对于每次增量是不同的需求,聚合函数就非常的有用。另外一个例子用来统计调用vfs_read的数据量:

global data_count[]

probe begin {
print("start profiling.");
} probe kernel.function("vfs_read") {
data_count[execname()] <<< $count;
} probe timer.ms() {
print(".");
} probe timer.ms() { # seconds
exit();
} probe end {
print("\n");
foreach (prog in data_count) {
printf("%15s : avg:%-8d cnt:%-12d sum:%-12d\n",
prog,
@avg(data_count[prog]),
@count(data_count[prog]),
@sum(data_count[prog]));
}
}

输出:

root@userver:~/stp# stap vfs-read-stat.stp
start profiling.....................
top : avg: cnt: sum:
in:imklog : avg: cnt: sum:
sshd : avg: cnt: sum:
stapio : avg: cnt: sum:
acpid : avg: cnt: sum:
bash : avg: cnt: sum:
systemd-udevd : avg: cnt: sum:

Tapset

tapset是一些systemtap脚本文件,存在于/usr/share/systemtap/tapset。

符号选择

当用户执行脚本时如果发现符号没定义那么会在tapset内进行搜索,其中还有些文件夹,其名称代表了kernel体系架构名称或者kernel版本名称。搜索匹配是具体到泛化的过程,跟路由IP选择一样,如果有精确的选择则优先选择可以精确匹配的,不行则在采用一般脚本中的定义,如都没找到则报错。不过不知为什么按着tutorial上的做依然提示找不到。。。

跟踪点别名

global groups

probe syscallgroup.io =
syscall.open, syscall.close, syscall.read, syscall.write
{
groupname = "io";
} probe syscallgroup.process =
syscall.fork, syscall.execve
{
groupname = "process"
} probe syscallgroup.* {
groups[pid(), execname() . "/" . groupname]++;
} probe end {
foreach ([id, eg+] in groups) {
printf("%5d %-20s %d\n", id, eg, groups[id, eg])
}
}

嵌入C代码

用户脚本中嵌入C语言的脚本,在运行时需要使用-g选项。

  1. Do not dereference pointers that are not known or testable valid. (不要随意对指针解引用)
  2. Do not call any kernel routine that may cause a sleep or fault. (不要调用那些会引起阻塞或者睡眠的函数)
  3. Consider possible undesirable recursion, where your embedded C function calls a routine that may be the subject of a probe. If that probe handler calls your embedded C function, you may suffer infinite regress. Similar problems may arise with respect to non-reentrant locks.   (不要调用会引起自身脚本无限循环的调用)
  4. If locking of a data structure is necessary, use a trylock type call to attempt to take the lock. If that fails, give up, do not block.(获取锁时先用trylock类型的调用尝试)

头文件可以使用

%{ %}方式在脚本开头引入。

function get_msg:string (id:long) %{
snprintf(STAP_RETVALUE, MAXSTRINGLEN, "helloworld %ld\n(%d)\n", (long)STAP_ARG_id, MAXSTRINGLEN);
%} probe begin {
print(get_msg());
}

输出:

# stap -g embedded-c.stp
helloworld
()

Linux 调试: systemtap的更多相关文章

  1. 掌握 Linux 调试技术

    掌握 Linux 调试技术 在 Linux 上找出并解决程序错误的主要方法 Steve Best (sbest@us.ibm.com)JFS 核心小组成员,IBM 简介: 您可以用各种方法来监控运行着 ...

  2. Linux 调试打印时间和颜色

    Linux调试打印时间和颜色 #include <sys/time.h> #include <unistd.h> void print_time(void) { struct ...

  3. Linux调试分析诊断利器——strace

    strace是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序. 在Linux系统中,用户程 ...

  4. linux 调试利器gdb, strace, pstack, pstree, lsof

    1) 如何使用strace+pstack利器分析程序性能? http://www.cnblogs.com/bangerlee/archive/2012/04/30/2476190.html 此文有详细 ...

  5. 掌握 Linux 调试技术【转】

    转自:https://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html 您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试 ...

  6. 内核调试 SystemTap

    http://www.cnblogs.com/wangkangluo1/archive/2012/06/26/2562971.html   相关技术:utrace, probe, ftrace, dt ...

  7. 蜂鸟E203系列——Linux调试(GDB+Openocd)

    欲观原文,请君移步 本文基于文章<蜂鸟E203系列--利用 Hbrid-E-SDK 环境开发程序> GDB 简介 GDB(GNU Project Debugger),是 GNU 工具链中的 ...

  8. Linux调试介绍

    1. 介绍 本文介绍了调试的一些常用函数和工具 2. 函数 用户态函数: backtrace()/backtrace_symbols() 内核态函数: dump_stack() 3. 工具 工具: g ...

  9. linux 调试常用命令

    top 参数 1 ,查看多核cpu  也可用 mpstat -P ALL pstate PID 查看进程堆栈 pmap -x PID 查看进程 内存段 ldd  XXX.so 查看 .so 的link ...

随机推荐

  1. ajax在php中应用实例

    1,ajax分为$.ajax(),$.get(),$.post(),$.getJSON() 几种形式,实例如下: <html> <meta http-equiv="Cont ...

  2. ajax post 400 bad request

    是前端ajax没有加声明:contentType:'application/json',

  3. C# 5.0-.Net新特性

    调用者信息特性 CallerMemberNameAttribute | CallerFilePathAttribute | CallerLineNumberAttribute .NET Framewo ...

  4. Python Django 的学习资料

    十分有用的链接: 链接1:http://www.cnblogs.com/wupeiqi/articles/5433893.html   (银角大王) 链接2:https://www.cnblogs.c ...

  5. 语义分割Semantic Segmentation研究综述

    语义分割和实例分割概念 语义分割:对图像中的每个像素都划分出对应的类别,实现像素级别的分类. 实例分割:目标是进行像素级别的分类,而且在具体类别的基础上区别不同的实例. 语义分割(Semantic S ...

  6. 认识AngularJS

    学习AngularJS所需技能 HTML & CSS JavaScript 为什么要使用AngularJS 如果你想用JavaScript制作动态Web站点,使用AngularJS有以下优点: ...

  7. Vim实用技巧系列 - 开篇

    作者使用Vim已经有一段时间了,深深觉得它是一个非常强大的编辑器,使用习惯之后效率很高.最大的缺点是学习曲线比较陡峭.因此作者希望分享一些自己在实际使用中的经验来帮助初学者,同时也帮助作者自己学习. ...

  8. 关于冒泡排序的Java代码实现

    一.排序算法的历史: 排序算法的发展历史几乎和计算机的发展历史一样悠久,而且直到今天,世界范围内依然有计算机科学家正在研究着排序的算法,由此可见排序算法的强大魅力.   我们现在介绍的排序算法都是前任 ...

  9. 《Algorithms算法》笔记:优先队列(1)——API和初等实现

    1.优先队列的API和初等实现 做一个总结: 栈 :先进后出 队列 :先进先出 随机队列 : 随机出 优先队列:每次出来的是最大值或最小值 1.1优先队列的API 优先队列在很多场合都有用, 比如:在 ...

  10. Android Studio SVN的使用

    一 SVN的配置 这篇文章使用的Android studio版本为1.4 RC3. 我选择的是TortoiseSVN,版本为1.8,不要选择1.9版本(目前的最新版),因为如果你安装的是1.9版本当你 ...