安装与配置

在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. elasticsearch索引目录设置

    path.data and path.logs If you are using the .zip or .tar.gz archives, the data and logs directories ...

  2. jQuery核心函数的四种不同用法

    核心函数根据实参的不同,有四种不同的用法. ①传一个函数作为参数 例如:$(function(){}) 作用:和window.onload = function(){}类似,它会在文档加载完成之后运行 ...

  3. 话谈C#第一天

    今天是第一次接触C#,由于长时间的做Java开发,突然转到C#非常的不自然,但是也有了一些收获,给大家分享一下 using System; using System.Collections.Gener ...

  4. pythonweb框架Flask学习笔记02-一个简单的小程序

    #-*- coding:utf-8 -*- #导入了Flask类 这个类的实例将会是我们的WSGI应用程序 from flask import Flask #创建一个Flask类的实例 第一个参数是应 ...

  5. (转)飘逸的python - 增强的格式化字符串format函数

    原文:https://blog.csdn.net/handsomekang/article/details/9183303 Python字符串格式化--format()方法-----https://b ...

  6. AT24Cxx学习笔记

    AT24Cxx是E2PRom的一个系列: 存储空间大小:AT24C02——2Kbit(256Bytes).AT24C04——4Kbit(512Bytes).AT24C08——8Kbit(1024Byt ...

  7. SpringBoot学习之自动装配

    在前面使用SSM集成时,我们可以使用注解实现无配置化注入,但是这种依赖被进行“人工干预了的”,换句话就是说我们手动进行装配,那么此时还没有达到SpringBoot这种自动装配的效果,那么究竟Sprin ...

  8. 查看LINUX 系统硬件等详细信息

    转载这位朋友[地址] 几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) mor ...

  9. Postman—命令执行脚本及生成报告

    前言 前面的应用中,都是在postman图形界面工具里面进行测试,但是有时候我们需要把测试脚本集成到CI平台,或者在非图形界面的系统环境下进行测试,那么我们该如果处理呢 通过newman来执行post ...

  10. ES6-let、const和块级作用域

    1.介绍 总的来说,ES6是在ES2015的基础上改变了一些书写方式,开放了更多API,这样做的目的最终还是为了贴合实际开发的需要.如果说一门编程语言的诞生是天才的构思和实现,那它的发展无疑就是不断填 ...