Ftrace学习笔记与实践

ftrace详解

​ ftrace是一款内核自带的调试工具在2.6内核上就有了,可以用来辅助定位内核问题。详情可以看ftrace的官方文件。

目前ftrace支持的类型有:

  • function:函数调用跟踪追踪所有内核函数
  • function_graph:函数图跟踪在函数的入口和退出上的迹线。 然后,它提供了绘制类似于C代码源的函数调用图的能力。
  • blk:块追踪
  • hwlat:硬件延迟示踪器用于检测硬件是否会产生任何延迟。 请参见下面的“硬件延迟检测器”部分。
  • irqsoff:跟踪不可中断区域,记录最大延迟,使用这个tracer会启用延时的选项查看该跟踪。
  • preemptoff:与irqsoff类似,但跟踪并记录抢占时间。
  • preemptirqsoff:可两者同时记录
  • wakeup:跟踪并记录在唤醒最高优先级任务后调度所需的最大延迟。跟踪所有的任务,作为平均开发人员预期。
  • wakeup_rt:记录RT任务所需最大延时
  • wakeup_dl
  • mmiotrace:用于跟踪二进制模块的特殊跟踪程序。它将跟踪模块对硬件的所有调用。它从I/O写入和读取的所有内容。
  • branc:当跟踪内核中可能的/不可能的调用时,可以配置此跟踪程序。它将跟踪可能的和不可能的分支何时被击中,以及它对正确性的预测是否正确。
  • nop:这是“无踪迹”追踪器。要从跟踪中删除所有跟踪程序,只需将“nop”回显到当前的跟踪程序中。c

Ftrace的基本用法

  1. 选择一种tracer
  2. 使能 ftrace
  3. 执行需要 trace 的应用程序,比如需要跟踪 ls,就执行 ls
  4. 关闭 ftrace
  5. 查看 trace 文件

查看系统支持的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的输出信息主要保存在三个文件中

  • Trace,该文件保存 ftrace 的输出信息,其内容可以直接阅读。
  • latency_trace,保存与 trace 相同的信息,不过组织方式略有不同。主要为了用户能方便地分析系统中有关延迟的信息。
  • trace_pipe 是一个管道文件,主要为了方便应用程序读取 trace 内容。算是扩展接口吧。

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继续跟踪。

Function配置:

available_filter_functions:列出当前可以跟踪的内核函数,不在该文件中列出的函数,无法跟踪其活动
enabled_functions:显示有回调附着的函数名称。
function_profile_enabled:打开此选项,在trace_stat中就会显示function的统计信息。
set_ftrace_filter:用于指定跟踪的函数
set_ftrace_notrace:用于指定不跟踪的函数
set_ftrace_pid:用于指定要跟踪特定进程的函数

Function_graph配置:

max_graph_depth:函数嵌套最大深度,默认不限制
set_graph_function:显示函数调用关系,使用function_graph跟踪器默认显示函数调用关系
set_graph_notrace:不跟踪特定的函数嵌套调用

Stack trace设置:

stack_max_size:当使用stack跟踪器时,记录产生过的最大stack size
stack_trace:显示stack的back trace
stack_trace_filter:设置stack tracer不检查的函数名称

event trace配置:

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。

Ftrace实例

function tracer实例:

设置 current_tracer 为 function

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 

打开 trace 开关,开始 trace

提取 trace 结果

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函数

function_graph Trace 实例

与function类似设置function_graph的方式如下:

    # cat available_tracers
    hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
    # echo function_graph > current_tracer

set_graph_function 表示要跟踪的函数:

    # echo dev_attr_show > set_graph_function
    # echo 1 > tracing_on

捕捉到的trace:

    # 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实例

​ trace event 就是利用 ftrace 框架,实现低性能损耗,对执行流无影响的一种信息输出机制。相比 printk,trace event:

  • 不开启没有性能损耗
  • 开启后不影响代码流程
  • 不需要重新编译内核即可获取 debug 信息

系统支持的所有 trace event 都位于 /sys/kernel/debug/tracing/events 目录。

设置trace event

​ 打开 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

添加自己的trace信息

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开关

用户态

​ 用户程序可以很灵活的控制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查看它支持的关键字

你可能感兴趣的:(ftrace,内核,linux)