ftrace是一款内核自带的调试工具在2.6内核上就有了,可以用来辅助定位内核问题。详情可以看ftrace的官方文件。
目前ftrace支持的类型有:
查看系统支持的tracer类型:
root@test-PC:/sys/kernel/debug/tracing# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
关闭tracer:
root@test-PC:/sys/kernel/debug/tracing# echo 0 > tracing_on
设置要跟踪的函数:
root@test-PC:/sys/kernel/debug/tracing# echo dev_attr_show > set_ftrace_filter
缺省目标为所有可跟踪的内核函数,还可以使用简单格式的通配符
begin*选择所有名字以 begin 字串开头的函数
middle选择所有名字中包含 middle 字串的函数
*end选择所有名字以 end 字串结尾的函数
通过该文件还可以指定属于特定模块的函数,这要用到 mod 指令。指定模块的格式为
si_detach [dhd]
echo ‘:mod:dhd’ > set_ftrace_filter
提取trace结果
cat trace
首先需要挂载debugfs文件系统命令如下:
# mkdir /debug
# mount -t debugfs nodev /debug
此时您将在 /debug 目录下看到 tracing 目录。 Ftrace 的控制接口就是该目录下的文件。tracer的控制文件叫做current_tracer,将需要使用的tracer的名字写入这个文件就可以使用了。
ftrace的输出信息主要保存在三个文件中
在/sys/kernel/debug/tracing目录下,我们会看到ftrace通过debugfs文件系统提供的一些ftrace的控制和输出文件,下面对这些文件进行简单的介绍:
available_events:列出系统中所有可用的Trace events,分两个层级,用冒号隔开。
current_tracer:用于设置或者显示当前使用的跟踪器列表。系统启动缺省值为nop,使用echo将跟踪器名字写入即可打开。可以通过写入nop重置跟踪器
buffer_size_kb:单个cpu跟踪缓存大小
buffer_total_size_kb:显示所有的跟踪缓存大小,不同之处在于buffer_size_kb是单个CPU的,buffer_total_size_kb是所有CPU的和
free_buffer:此文件用于在一个进程被关闭后,同时释放RingBuffer内存,并将调整大小到最小值
trace:查看获取到的跟踪信息的接口,echo > trace可以清空当前RingBuffer。
trace_pipe:实时输出和trace一样的内容,但是此文件输出Trace同时将RingBuffer中的内容删除,这样就避免了RingBuffer的溢出。可以通过cat trace_pipe > trace.txt 保存文件
trace_options:控制Trace打印内容或者操作跟踪器,可以通过trace_options添加很多附加信息
options:trace选项的一系列文件,和trace_options配合使用。
tracing_on:用于控制跟踪打开或停止,0停止跟踪,1继续跟踪。
available_filter_functions:列出当前可以跟踪的内核函数,不在该文件中列出的函数,无法跟踪其活动
enabled_functions:显示有回调附着的函数名称。
function_profile_enabled:打开此选项,在trace_stat中就会显示function的统计信息。
set_ftrace_filter:用于指定跟踪的函数
set_ftrace_notrace:用于指定不跟踪的函数
set_ftrace_pid:用于指定要跟踪特定进程的函数
max_graph_depth:函数嵌套最大深度,默认不限制
set_graph_function:显示函数调用关系,使用function_graph跟踪器默认显示函数调用关系
set_graph_notrace:不跟踪特定的函数嵌套调用
stack_max_size:当使用stack跟踪器时,记录产生过的最大stack size
stack_trace:显示stack的back trace
stack_trace_filter:设置stack tracer不检查的函数名称
available_events:列出系统中所有可用的Trace events,分两个层级,用冒号隔开。
events/:系统Trace events目录,在每个events下面都有enable、filter和fotmat。enable是开关;format是events的格式,然后根据格式设置 filter
set_event:将Trace events名称直接写入set_event就可以打开。
set_event_pid:指定追踪特定进程的events。
root@test-PC:/sys/kernel/debug/tracing# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
root@test-PC:/sys/kernel/debug/tracing# echo function > current_tracer
#### 设置要 trace 的函数
```bash
root@test-PC:/sys/kernel/debug/tracing# echo ktime_get> set_ftrace_filter
root@test-PC:/sys/kernel/debug/tracing# cat trace >> /tmp/trace.txt
#### Function tracer的输出
Function tracer 跟踪调用函数流程其 trace文件格式如下:
```bash
# tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
bash-4251 [01] 10152.583854: path_put <-path_walk
bash-4251 [01] 10152.583855: dput <-path_put
bash-4251 [01] 10152.583855: _atomic_dec_and_lock <-dput
第四列显示的是内核函数名和它的上层函数调用
如上例所示,path_walk调用了path_put然后path_put调用了dput函数
# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
# echo function_graph > current_tracer
# echo dev_attr_show > set_graph_function
# echo 1 > tracing_on
# cat trace
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) | dev_attr_show() {
0) | energy_uj_show() {
0) | get_energy_counter [intel_rapl_common]() {
0) | cpus_read_lock() {
0) | _cond_resched() {
0) 0.283 us | rcu_all_qs();
0) 0.880 us | }
0) 1.521 us | }
0) | rapl_read_data_raw [intel_rapl_common]() {
0) | rapl_msr_read_raw [intel_rapl_msr]() {
0) | rdmsrl_safe_on_cpu() {
0) | rdmsr_safe_on_cpu() {
0) 0.271 us | __init_waitqueue_head();
0) | smp_call_function_single_async() {
0) | generic_exec_single() {
0) | __rdmsr_safe_on_cpu() {
0) | complete() {
0) 0.268 us | _raw_spin_lock_irqsave();
0) | __wake_up_locked() {
0) 0.298 us | __wake_up_common();
0) 0.836 us | }
0) 0.281 us | _raw_spin_unlock_irqrestore();
0) 2.476 us | }
0) 3.610 us | }
0) 4.315 us | }
0) 4.941 us | }
0) | wait_for_completion() {
0) | _cond_resched() {
0) 0.270 us | rcu_all_qs();
0) 0.800 us | }
0) 0.271 us | _raw_spin_lock_irq();
0) 1.920 us | }
0) 8.231 us | }
0) 8.824 us | }
0) 9.413 us | }
0) + 10.670 us | }
0) 0.269 us | cpus_read_unlock();
0) + 13.757 us | }
0) + 15.976 us | }
0) + 17.029 us | }
我们跟踪的是 dev_attr_show 函数,但是 function_graph tracer 会跟踪函数内的调用关系和函数执行时间,可以协助我们确定代码执行流程。比如一个函数内部执行了很多函数指针,不能确定到底执行的是什么函数,可以用 function_graph tracer 跟踪一下。
trace event 就是利用 ftrace 框架,实现低性能损耗,对执行流无影响的一种信息输出机制。相比 printk,trace event:
系统支持的所有 trace event 都位于 /sys/kernel/debug/tracing/events 目录。
打开 sched_switch event。顾名思义,sched_switch 可以监控系统内进程切换事件。
# cd /sys/kernel/debug/tracing/events/sched/sched_switch
# echo 1 > tracing
# echo sched_wakeup >> /sys/kernel/debug/tracing/set_event
# echo 0 > trace
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 221/221 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
-0 [003] d... 3408.075314: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=chrome next_pid=4737 next_prio=120
chrome-4737 [003] d... 3408.075465: sched_switch: prev_comm=chrome prev_pid=4737 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120
-0 [003] d... 3408.100181: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=chrome next_pid=4737 next_prio=120
chrome-4737 [003] d... 3408.100333: sched_switch: prev_comm=chrome prev_pid=4737 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120
-0 [003] d... 3408.125190: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=chrome next_pid=4737 next_prio=120
chrome-4737 [003] d... 3408.125339: sched_switch: prev_comm=chrome prev_pid=4737 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120
-0 [003] d... 3408.150474: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=chrome next_pid=4737 next_prio=120
chrome-4737 [003] d... 3408.150680: sched_switch: prev_comm=chrome prev_pid=4737 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120
-0 [003] d... 3408.175599: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=chrome next_pid=4737 next_prio=120
root@test-PC:/sys/kernel/debug/tracing/events/sched/sched_switch# cat format
name: sched_switch
ID: 323
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:char prev_comm[16]; offset:8; size:16; signed:1;
field:pid_t prev_pid; offset:24; size:4; signed:1;
field:int prev_prio; offset:28; size:4; signed:1;
field:long prev_state; offset:32; size:8; signed:1;
field:char next_comm[16]; offset:40; size:16; signed:1;
field:pid_t next_pid; offset:56; size:4; signed:1;
field:int next_prio; offset:60; size:4; signed:1;
print fmt: "prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d", REC->prev_comm, REC->prev_pid, REC->prev_prio, (REC->prev_state & ((((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) - 1)) ? __print_flags(REC->prev_state & ((((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) - 1), "|", { 0x0001, "S" }, { 0x0002, "D" }, { 0x0004, "T" }, { 0x0008, "t" }, { 0x0010, "X" }, { 0x0020, "Z" }, { 0x0040, "P" }, { 0x0080, "I" }) : "R", REC->prev_state & (((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) ? "+" : "", REC->next_comm, REC->next_pid, REC->next_prio
从上面的 format 信息可以看出 sched_switch 打印的信息格式,基于上面提供的关键字可以实现信息过滤,比如下面的过滤命令可以只显示 chrome 进程的切换信息。
# cd /sys/kernel/debug/tracing/events/sched/sched_switch
# echo "prev_comm == 'chrome' || next_comm == 'chrome'" > filter
1、trace event
trace event的格式可以参考内核源码sample/trace_event
2、trace_printk
trace_printk 是一个函数,它的调用方式与 printk 一模一样,只是 trace_printk 输出信息到 trace 文件,而 printk 输出到终端或者其它的缓存 buffer 中。
trace_printk 延迟更短,几乎不影响原代码流程,而 printk 的延迟要大很多,且它的实现体系中也比 trace_printk 流程长,在中断、调度系统中调用 printk 几乎不可行。
如果添加 printk 后问题消失了,那么请试试 trace_printk。
查看函数调用栈是内核调试最最基本得需求,常用方法:
函数内部添加 WARN_ON(1)
ftrace
trace 函数的时候,设置 echo 1 > options/func_stack_trace 即可在 trace 结果中获取追踪函数的调用栈。
以 dev_attr_show 函数为例,看看 ftrace 如何帮我们获取调用栈:
$ cd /sys/kernel/debug/tracing
$ sudo -s
# echo 0 > tracing_on
# echo function > current_tracer
# echo dev_attr_show > set_ftrace_filter
// 设置 func_stack_trace
# echo 1 > options/func_stack_trace
# echo 1 > tracing_on
# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 8/8 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
top-3008 [003] .... 621.507777: dev_attr_show <-sysfs_kf_seq_show
top-3008 [003] .... 621.507784: <stack trace>
=> dev_attr_show
=> sysfs_kf_seq_show
=> kernfs_seq_show
=> seq_read
=> kernfs_fop_read
=> __vfs_read
=> vfs_read
=> ksys_read
=> __x64_sys_read
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
=> 0
跟踪某个特点的进程只需要将进程的pid设置到set_event_pid/set_ftrace_pid
即可但如果这个进程时间很短无法知道它的pid,所以无法用这种方法过滤。
对于这种问题可以使用一种脚本化的小技巧
sh -c "echo $$ > set_ftrace_pid; echo 1 > tracing_on; kill xxx; echo 0 > tracing_on"
# cd /sys/kernel/debug/tracing
# echo 'dev_attr_*' > set_ftrace_filter
# cat set_ftrace_filter
dev_attr_store
dev_attr_show
用法为:echo xxx >> set_ftrace_filter,例如,先设置 dev_attr_*,再将 ip_rcv 追加到跟踪函数中:
# cd /sys/kernel/debug/tracing
# echo 'dev_attr_*' > set_ftrace_filter
# cat set_ftrace_filter
dev_attr_store
dev_attr_show
echo ip_rcv >> set_ftrace_filter
格式为:
,例如,过滤 ext3 module 的 write* 函数:
$ echo 'write*:mod:ext3' > set_ftrace_filter
有些问题是需要将用户态、内核态的行为联系在一起的,但是 printf/printk 天然是分家的,用户态程序只需要打开 trace_marker 节点可以向其中写入内容,写入的内容会体现在 trace 文件中,与内核态的各种 trace 融合在一起,提供时间线、事件参考。
# cd /sys/kernel/debug/tracing
# echo 'hello ftrace' > trace_marker
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 1/1 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
用户程序可以很灵活的控制trace开关,因为可以在程序中打开tracing_on
文件,灵活控制何时enable,何时disable。
内核态控制trace enable/disable的作用是根据条件及时停止,更准确的获取现场信息,同时防止后面的无效信息冲掉有效信息。这种功能是通过 set_ftrace_filter 实现的,控制范式:
简单示例:遇到 __schedule_bug 函数后关闭 trace
# echo '__schedule_bug:traceoff' > set_ftrace_filter
# echo '__schedule_bug:traceoff:5' > set_ftrace_filter //五次后再执行traceoff
# echo '!try_to_wake_up:enable_event:sched:sched_switch:0' > set_ftrace_filter //删除事件
set_ftrace_fileter还支持snapshot(快照)、enable_event(事件追踪)等其他关键字,可以通过查看filter-commands查看它支持的关键字