安装 systemtap
yum -y install systemtap
编译开关的设置
用户态程序的编译
并不是每个函数都可以探测的。
只有使用了 -g 的调试开关。才可以支持探测。
而且 -O 的优化开关,也会令到探测点减少。
所以最好在编译的时候使用 -O0 -g3,关闭优化,还有使用高一点的调试级别。
内核的编译
要想编译一个支持Systemtap的内核,必须配置这些内核选项:
Kernel hacking --->
[*] Kernel debugging
[*] Compile the kernel with debug info
Instrumentation Support --->
[*] Kprobes (EXPERIMENTAL)
General setup --->
[*] Kernel->user space relay support (formerly relayfs)
可以用以下符号grep生成的配置文件.config来确认这些配置是否成功:
如果成功,它们应当都为 Y.
# file: .config
CONFIG_DEBUG_INFO
CONFIG_KPROBES
CONFIG_RELAY
查询函数探测点
使用 stap 的 -l, -L 可以打印出支持的 探测点。
-g:专家模式。可以打印更多的探测点。
-l:只打印出 函数探测点 的函数函数名称,路径,和行号。
输出格式如下
function("<function_name>@<source_file_path>:<line_number>")。
-L:在
-l的基础上,增加探测的变量列表。
输出格式如下
function("<function_name>@<source_file_path>:<line_number>") <function_variable_list>。
使用方法:
# 查询内核的系统调用
stap -gL 'syscall.*'
# 查询内核的函数探测点
stap -gL 'kernel.function("<function_name>")'
stap -gL 'kernel.function("<function_name>").call' # 函数入口
stap -gL 'kernel.function("<function_name>").return' # 函数出口
# 查询内核模块
stap -gL 'module("<module_name>").function("<function_name>")'
# 查询可执行文件的函数探测点
stap -L 'process("<executable_file_path>").function("<function_name>")'
stap -L 'process("<executable_file_path>").function("<function_name>").call' # 函数入口
stap -L 'process("<executable_file_path>").function("<function_name>").return' # 函数出口
# 查询动态链接库的函数探测点
stap -L 'process("<shared_library_file_path>").function("<function_name>")'
stap -L 'process("<shared_library_file_path>").function("<function_name>").call' # 函数入口
stap -L 'process("<shared_library_file_path>").function("<function_name>").return' # 函数出口
# 查询语句探测点
stap -L 'process("<shared_library_file_path>").statement("<function_name>:@<source_file_path>:*")'
<executable_file_path>:可执行文件的路径
<shared_library_file_path>:动态链接库的路径
注意:一定要是ldconfig -p所登记的路径。
<function_name>:函数名称。可以使用通配字符匹配。如果要显示所有函数,可以使用 “*”。
例子:
## 查询系统调用
stap -gL 'syscall.*' | grep sendmsg
> syscall.compat_sys_sendmsg name:string s:long msg_uaddr:long flags:long flags_str:string argstr:string $fd:int $msg:struct compat_msghdr* $flags:unsigned int
> syscall.sendmsg name:string s:long msg_uaddr:long flags:long flags_str:string argstr:string $fd:long int $msg:long int $flags:long int
stap -gL 'syscall.sendmsg.return'
> syscall.sendmsg.return name:string retstr:string $return:long int $fd:long int $msg:long int $flags:long int
## 查询内核模块
stap -gL 'module("xt_REDIRECT").function("redirect_tg4")'
## 查询内核函数探测点
stap -gL 'kernel.function("__neigh_create")'
> kernel.function("__neigh_create@net/core/neighbour.c:463") $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool
stap -gL 'kernel.function("__neigh_create").call'
> kernel.function("__neigh_create@net/core/neighbour.c:463").call $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool
stap -gL 'kernel.function("__neigh_create").return'
> kernel.function("__neigh_create@net/core/neighbour.c:463").return $return:struct neighbour* $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool
## 查询内核语句探测点
stap -gL 'kernel.statement("__neigh_create@net/core/neighbour.c:*")'
> kernel.statement("__neigh_create@net/core/neighbour.c:464") $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool $key_len:int
> ...
> kernel.statement("__neigh_create@net/core/neighbour.c:489") $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool $key_len:int $error:int
> ...
> kernel.statement("__neigh_create@net/core/neighbour.c:521") $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool $n1:struct neighbour* $nht:struct neigh_hash_table*
> ...
> kernel.statement("__neigh_create@net/core/neighbour.c:548") $tbl:struct neigh_table* $pkey:void const* $dev:struct net_device* $want_ref:bool
## 查询用户态函数
which ping
> /usr/bin/ping
stap -gL 'process("/usr/bin/ping").function("*")'
> process("/usr/bin/ping").function("__do_global_dtors_aux")
> process("/usr/bin/ping").function("__libc_csu_fini")
> process("/usr/bin/ping").function("__libc_csu_init")
> process("/usr/bin/ping").function("__schedule_exit")
> process("/usr/bin/ping").function("_fini")
> process("/usr/bin/ping").function("_init")
> process("/usr/bin/ping").function("_start")
> ...
> process("/usr/bin/ping").function("main")
> process("/usr/bin/ping").function("main_loop")
> ...
> process("/usr/bin/ping").function("pinger")
> ...
使用调试脚本
用户态调试
stap --ldd -x <pid> <stap_script_file_path>
--ldd:用于调试动态链接库
<pid>:进程号
<stap_script_file_path>:调试脚本路径
编写调试脚本
探测点
| 探测点 | 描述 |
|---|---|
| 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. |
| kernel.function(“xx”).inline {} | 指定函数被内联时进入 |
| kernel.function(“xx”).call {} | 指定函数被调用是进入(不含内联) |
| kernel.function("").return{} | 可以在该函数返回时执行。 |
简单的例子:
计数4秒。每一秒计数一次。
/* global counter */
global count;
/* header */
probe begin
{
count = 0;
printf("--begin--\n");
printf("count = %d\n", count);
}
/* footer */
probe end
{
printf("\n");
printf("---end---\n");
printf("count = %d\n", count);
}
/* increasing counter every 1 second */
probe timer.ms(1000) {
count++;
printf(".");
}
/* exit after 4 seconds */
probe timer.ms(4000)
{
exit()
}
将每隔5秒钟输出系统调用的最多的20个系统调用。
#!/usr/bin/env stap
#
# This script continuously lists the top 20 systemcalls on the system
#
global syscalls
function print_top () {
cnt=0
log ("SYSCALL\t\t\t\tCOUNT")
foreach ([name] in syscalls-) {
printf("%-20s\t\t%5d\n",name, syscalls[name])
if (cnt++ == 20)
break
}
printf("--------------------------------------\n")
delete syscalls
}
probe kernel.function("sys_*") {
syscalls[probefunc()]++
}
# print top syscalls every 5 seconds
probe timer.ms(5000) {
print_top ()
}
基础函数
| 基础函数 | 描述 |
|---|---|
| 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. |
| gettimeofday_ms | Number of milli-seconds since epoch. |
| gettimeofday_us | Number of micro-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. |
| print_backtrace() | If possible, print a kernel backtrace. |
| print_ubacktrace() | If possible, print a user-space backtrace. |
| thread_indent | 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());
}
变量
变量默认的都是局部变量,即每个处理函数内的变量是不共享的。
使用全局变量的话,要在开始使用global关键字进行定义。
变量是弱类型的,可以相互转换但是要手工显式进行。
| 变量 | 描述 |
|---|---|
| $varname | 引用变量varname |
| $var->field" | 引用结构的成员变量 |
| $var[N] | 引用数组的元素 |
| &$var | 变量的地址 |
| @var(“varname”) | 引用变量varname |
| @var(“var@src/file.c”) | 引用src/file.c在被编译时的全局变量varname |
| @var(“varname@file.c”)->field | 引用结构的成员变量 |
| @var(“var@file.c”)[N] | 引用数组的元素 |
| &@var(“var@file.c”) | 变量的地址 |
| v a r var var | provide a string that includes the values of basic type values,不会打印成员变量 |
| v a r var var$ | provide a string that includes all values of nested data types,会打印成员变量 |
| $$vars | 一个包含所有函数参数、局部变量的字符串。 |
| $$locals | 一个包含所有局部变量的字符串,$$vars子集,仅打印local变量 |
| $$params | 一个包含所有函数参数的字符串,$$vars子集,仅包含函数参数 |
| $$return | 仅在return probes存在,如果没有返回值,则是空串 |
| 变量 | 描述 |
|---|---|
| $$vars | 类似于 sprintf(“parm1=%x,… ,parmN=%x var1=%x,… ,varN=%x”, parm1,…, parmN, var1,…, varN) |
| $$locals | 类似于 sprintf(“var1=%x,… ,varN=%x”, var1,… ,varN) |
| $$params | 类似于 sprintf(“parm1=%x,… ,parmN=%x”, parm1,… ,parmN) |
| $$return | 类似于 sprintf(“return=%x”, $return) |
ref:
Systemtap
SystemTap Beginner
SystemTap使用技巧【一】
SystemTap Beginner
Linux 调试: systemtap
内核调试神器SystemTap — 探测点与语法(二)

本文介绍了如何安装和使用Systemtap进行调试,包括设置编译开关、查询函数探测点、编写调试脚本等。在用户态程序编译时需开启调试开关并关闭优化,内核编译需配置特定选项。Systemtap可用于打印探测点、调试动态链接库,并提供了丰富的调试脚本编写示例。

1114

被折叠的 条评论
为什么被折叠?



