原文:https://sourceware.org/systemtap/tutorial/2_Tracing.html
2 Tracing
最简单地使用 systemTap 就是跟踪一个事件,比如在程序中插入打印语句。这通常是解决问题的第一步:通过查看发生事情的历史来探索。
这样比较简单,它只要求SystemTap在每个事件中打印一些内容。要用脚本语言表达这一点,您需要说明在哪里进行探测以及打印什么。
2.1 Where to probe
SystemTap支持许多内置事件。SystemTap附带的脚本库(每个脚本都称为“Tapset”)可以根据内置系列定义其他脚本。有关这些和许多其他探测点族的详细信息,请参见Stapprobes手册页。所有这些事件都使用统一的语法和点分隔的参数化标识符来命名:
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 |
假设您希望跟踪源文件中的所有函数条目和出口,比如内核中的 net/socket.c。kernel.function探测点让您可以轻松地表达这一点,因为SystemTap检查内核的调试信息,将对象代码与源代码关联起来。它就像一个调试器:如果可以命名或放置它,就可以对它进行探测。使用 kernel.function(“*@net/socket.c”)。调用函数项1和kernel.function(“*@net/socket.c”)。返回以获取匹配的出口。注意在函数名部分和后面的@filename部分使用通配符。如果要精确地限制搜索,还可以将通配符放入文件名,甚至添加冒号(:)和行号。由于SystemTap将在每个与探测点匹配的地方放置一个单独的探测,一些通配符可以扩展到数百或数千个探测,因此请注意您要求的内容。
识别探测点后,将显示SystemTap脚本的框架。probe关键字引入一个探测点,或它们的逗号分隔列表。下面的和括号包含所有列出的探测点的处理程序。
probe kernel.function("*@net/socket.c") { }
probe kernel.function("*@net/socket.c").return { }
您可以按原样运行此脚本,但如果处理程序为空,则不会有输出。把这两行放到一个新文件中。运行stap-v文件。随时使用^C终止它。(-v选项告诉SystemTap在处理期间打印更多详细的消息。尝试-h选项查看更多选项。)
由于您对输入和退出的每个函数感兴趣,因此应该为每个函数打印一行,其中包含函数名。为了使该列表易于读取,SystemTap应缩进行,以便其他跟踪函数调用的函数嵌套得更深。要将每个进程与可能同时运行的其他进程区分开来,SystemTap还应在行中打印进程ID。
SystemTap提供了各种上下文数据,可以进行格式化。它们通常作为函数调用出现在处理程序中,如图[*]中所示。有关这些函数和tapset库中定义的更多内容,请参见函数::*手册页,但下面是一个示例:
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. |
返回的值可以是字符串或数字。print()内置函数接受任何一个作为其唯一参数。或者,您可以使用c-style printf()内置的格式参数,其格式参数可能包括字符串的%s,数字的%d。printf和其他函数采用逗号分隔的参数。不要忘了结尾处的“n”。还有更多的打印/格式化功能。
Tapset库中一个特别方便的功能是线程缩进。给定一个indentation delta参数,它在内部为每个线程存储一个缩进计数器(tid()),并返回一个字符串,其中包含一些通用跟踪数据和适当数量的缩进空间。通用数据包括时间戳(自线程的初始缩进以来的微秒数)、进程名和线程ID本身。因此,它不仅给出了调用哪些函数的概念,还给出了调用这些函数的人,以及调用这些函数需要多长时间。图[*]显示了完成的脚本。它缺少对exit()函数的调用,因此当您希望停止跟踪时,需要用^C中断它。
1,使用-l选项systemtap列出所有以单词“nit”命名的内核函数。
2,使用与图[*]中相同的线程缩进探测处理程序跟踪一些系统调用(使用syscall.name和.return探测点)。使用
$$parms和$$return打印参数。解释结果。
3,通过从第一个探测中删除.call修饰符来更改图[*]。注意函数入口和函数返回现在不再匹配了。这是因为现在第一个探针将同时匹配正常函数条目和内联函数。尝试将.call修饰符放回原处,然后添加另一个probe,仅用于probe kernel.function(“*@net/socket.c”)。inline在probe处理程序中可以使用什么printf语句来在.call和.return线程缩进输出之间很好地显示inline函数项?