Linux 调试: systemtap
安装与配置
在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选项。
- Do not dereference pointers that are not known or testable valid. (不要随意对指针解引用)
- Do not call any kernel routine that may cause a sleep or fault. (不要调用那些会引起阻塞或者睡眠的函数)
- 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. (不要调用会引起自身脚本无限循环的调用)
- 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的更多相关文章
- 掌握 Linux 调试技术
掌握 Linux 调试技术 在 Linux 上找出并解决程序错误的主要方法 Steve Best (sbest@us.ibm.com)JFS 核心小组成员,IBM 简介: 您可以用各种方法来监控运行着 ...
- Linux 调试打印时间和颜色
Linux调试打印时间和颜色 #include <sys/time.h> #include <unistd.h> void print_time(void) { struct ...
- Linux调试分析诊断利器——strace
strace是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序. 在Linux系统中,用户程 ...
- linux 调试利器gdb, strace, pstack, pstree, lsof
1) 如何使用strace+pstack利器分析程序性能? http://www.cnblogs.com/bangerlee/archive/2012/04/30/2476190.html 此文有详细 ...
- 掌握 Linux 调试技术【转】
转自:https://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html 您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试 ...
- 内核调试 SystemTap
http://www.cnblogs.com/wangkangluo1/archive/2012/06/26/2562971.html 相关技术:utrace, probe, ftrace, dt ...
- 蜂鸟E203系列——Linux调试(GDB+Openocd)
欲观原文,请君移步 本文基于文章<蜂鸟E203系列--利用 Hbrid-E-SDK 环境开发程序> GDB 简介 GDB(GNU Project Debugger),是 GNU 工具链中的 ...
- Linux调试介绍
1. 介绍 本文介绍了调试的一些常用函数和工具 2. 函数 用户态函数: backtrace()/backtrace_symbols() 内核态函数: dump_stack() 3. 工具 工具: g ...
- linux 调试常用命令
top 参数 1 ,查看多核cpu 也可用 mpstat -P ALL pstate PID 查看进程堆栈 pmap -x PID 查看进程 内存段 ldd XXX.so 查看 .so 的link ...
随机推荐
- elasticsearch索引目录设置
path.data and path.logs If you are using the .zip or .tar.gz archives, the data and logs directories ...
- jQuery核心函数的四种不同用法
核心函数根据实参的不同,有四种不同的用法. ①传一个函数作为参数 例如:$(function(){}) 作用:和window.onload = function(){}类似,它会在文档加载完成之后运行 ...
- 话谈C#第一天
今天是第一次接触C#,由于长时间的做Java开发,突然转到C#非常的不自然,但是也有了一些收获,给大家分享一下 using System; using System.Collections.Gener ...
- pythonweb框架Flask学习笔记02-一个简单的小程序
#-*- coding:utf-8 -*- #导入了Flask类 这个类的实例将会是我们的WSGI应用程序 from flask import Flask #创建一个Flask类的实例 第一个参数是应 ...
- (转)飘逸的python - 增强的格式化字符串format函数
原文:https://blog.csdn.net/handsomekang/article/details/9183303 Python字符串格式化--format()方法-----https://b ...
- AT24Cxx学习笔记
AT24Cxx是E2PRom的一个系列: 存储空间大小:AT24C02——2Kbit(256Bytes).AT24C04——4Kbit(512Bytes).AT24C08——8Kbit(1024Byt ...
- SpringBoot学习之自动装配
在前面使用SSM集成时,我们可以使用注解实现无配置化注入,但是这种依赖被进行“人工干预了的”,换句话就是说我们手动进行装配,那么此时还没有达到SpringBoot这种自动装配的效果,那么究竟Sprin ...
- 查看LINUX 系统硬件等详细信息
转载这位朋友[地址] 几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) mor ...
- Postman—命令执行脚本及生成报告
前言 前面的应用中,都是在postman图形界面工具里面进行测试,但是有时候我们需要把测试脚本集成到CI平台,或者在非图形界面的系统环境下进行测试,那么我们该如果处理呢 通过newman来执行post ...
- ES6-let、const和块级作用域
1.介绍 总的来说,ES6是在ES2015的基础上改变了一些书写方式,开放了更多API,这样做的目的最终还是为了贴合实际开发的需要.如果说一门编程语言的诞生是天才的构思和实现,那它的发展无疑就是不断填 ...