安装与配置

在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. hdoj1045 Fire Net(二分图最大匹配)

    题意:给出一个图,其中有 . 和 X 两种,. 为通路,X表示墙,在其中放炸弹,然后炸弹不能穿过墙,问你最多在图中可以放多少个炸弹? 这个题建图有点复杂orz. 建图,首先把每一行中的可以放一个炸弹的 ...

  2. 【小程序云开发入门】quickStart

    开发者可以使用云开发开发微信小程序.小游戏,无需搭建服务器,即可使用云端能力. 云开发为开发者提供完整的云端支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现 ...

  3. iOS-Button图片和文字垂直居中【按钮图片和文字同时居中】

    以前不怎么有这样的需求,最近开发经常用到,所以就干脆封装一个这样的 Button 让图片和字体都垂直居中,重写layoutSubviews方法,来实现就可以,至于 layoutSubviews 方法什 ...

  4. SpringBoot学习笔记(一)基础

    Spring Boot理念 习惯优于配置.使用Spring Boot很容易创建一个独立运行(运行jar,内嵌servlet容器).准生产级别的基于Spring框架的项目,使用SpringBoot可以不 ...

  5. 解决git commit 遇到datached HEAD问题

    git detached HEAD 你可以认为 HEAD(大写)是”current branch”(当下的分支).当你用git checkout切换分支的时候,HEAD 修订版本重新指向新的分支. 有 ...

  6. P2149 Elaxia的路线

    P2149 Elaxia的路线 题意简述: 在一个n(n<=1500)个点的无向图里找两对点之间的最短路径的最长重合部分,即在保证最短路的情况下两条路径的最长重合长度(最短路不为一) 思路: 两 ...

  7. Tomcat 基本配置

    1.配置虚拟目录映射 推荐在 /conf/Catalina/localhost 下新建rand.xml方式建立虚拟目录 其中rand将会被当作映射对象,即外部访问路径. 例子:blog.xml < ...

  8. 如何虚拟机里安装Win8操作系统

    不多说,直接上干货! Windows Server 2003.2008.2012系统的安装 推荐网址:打开MSDN网站(http://msdn.itellyou.cn ) 关于给电脑换系统,很多人会花 ...

  9. ssh 登录进入 docker container

    1.Container安装ssh服务,博主的linux是centos ① 安装ssh sudo yum install openssh-server #安装ssh服务器 service sshd st ...

  10. glide 解决 golang.org/x/net 等依赖包无法获取

    知道glide有设置镜像功能,可以把某个依赖包的源地址切换为另一个地址,相当于切换到镜像地址,用于某些依赖包被墙的原因 之前碰到 golang.org/x/net,设置镜像: glide mirror ...