当我们定位到进程热点函数问题时,Ftrace是内核自带的调试工具,可以辅助定位内核问题。
从名字来看,ftrace = function trace,表示可以进行函数级的trace,随着发展Ftrace已经进化成通用的调试框架,可以实现多种trace跟踪的目的。
学习ftrace最好的还是官方文档,其地址如下所示:
https://www.kernel.org/doc/html/latest/trace/ftrace.html
一、Ftrace使用方法
1、Ftrace三板斧
1、设置 tracer 类型
2、设置 tracer 参数
3、开启tracer
目录介绍
文件 | 描述 |
---|---|
available_tracers | 可用跟踪器,hwlat blk function_graph wakeup_dl wakeup_rt wakeup function nop,nop 表示不使用跟踪器 |
current_tracer | 当前使用的跟踪器 |
function_profile_enabled | 启用函数性能分析器 |
available_filter_functions | 可跟踪的完整函数列表 |
set_ftrace_filter | 选择跟踪函数的列表,支持批量设置,例如 tcp、tcp 和 tcp 等 |
set_ftrace_notrace | 设置不跟踪的函数列表 |
set_event_pid | 设置跟踪的 PID,表示仅跟踪 PID 程序的函数或者其他跟踪 |
tracing_on | 是否启用跟踪,1 启用跟踪 0 关闭跟踪 |
trace_options | 设置跟踪的选项 |
trace_stat(目录) | 函数性能分析的输出目录 |
kprobe_events | 启用 kprobe 的配置 |
uprobe_events | 启用 uprobe 的配置 |
events ( 目录 ) | 事件(Event)跟踪器的控制文件:tracepoint、kprobe、uprobe |
trace | 跟踪的输出 (Ring Buffer) |
trace_pipe | 跟踪的输出;提供持续不断的数据流,适用于程序进行读取 |
后续的目录,如无特殊说明,都默认位于 /sys/kernel/debug/tracing/
根目录。
2、内核函数调用跟踪
进入 ftrace 工作目录:
cd /sys/kernel/debug/tracing
设置 tracer 类型为 function:
echo function > current_tracer
set_ftrace_filter 表示要跟踪的函数,这里我们只跟踪 dev_attr_show 函数:
echo dev_attr_show > set_ftrace_filter
开启全局的 tracer:
echo 1 > tracing_on
查看trace结果
cat trace
跟踪结果中标识了函数调用的任务名称、PID、CPU、标记位、时间戳及函数名字。上图中函数名称为dev_attr_show。
关闭跟踪
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo nop > /sys/kernel/debug/tracing/current_tracer
从上图可以看到 function trace 一个函数的方法基本就是三板斧:
- 设置 current_tracer 为 function
- 设置要 trace 的函数
- 打开 trace 开关,开始 trace
- 提取 trace 结果
3、查看函数的调用栈
使用场景:
前面我们获得了内核函数的调用,但是有些场景我们更可能希望获取调用该内核函数的流程(即该函数是在何处被调用),这需要通过设置 options/func_stack_trace
选项实现。
设置 echo 1 > options/func_stack_trace
后 即可在 trace 结果中获取追踪函数的调用栈。
#进入目录
cd /sys/kernel/debug/tracing
#先关闭跟踪
echo 0 > tracing_on
#设置跟踪类型以及跟踪的函数名称
echo function > current_tracer
echo dev_attr_show > set_ftrace_filter
#开启跟踪函数的调用栈
# echo 1 > options/func_stack_trace
#开启全局的跟踪
# echo 1 > tracing_on
其cat trace查看跟踪结果,如下图所示:
关闭
# 关闭
#进入目录
cd /sys/kernel/debug/tracing
#重置当前跟踪器为nop
sudo echo nop > current_tracer
#重置当前跟踪器的过滤条件
sudo echo > set_ftrace_filter
#关闭内核函数堆栈跟踪开关
sudo echo 0 > options/func_stack_trace
1、4 函数调用关系跟踪
使用场景:
如果想要分析内核函数调用的子流程(即本函数调用了哪些子函数,处理的流程如何),这时需要用到 function_graph
跟踪器,从字面意思就可看出这是函数调用关系跟踪。
设置 tracer 类型为 function_graph:
echo function_graph > current_tracer
set_graph_function 表示要跟踪的函数
echo dev_attr_show > set_graph_function
上述dev_attr_show是我们此次要跟踪的函数。
开启全局的tracer:
echo 1 > tracing_on
查看trace结果
cat trace
关闭跟踪
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo nop > /sys/kernel/debug/tracing/current_tracer
通过简单的例子,看到了 function/function_graph
tracer 的基本用法,但是在实际应用中会有比较棘手的问题和需求。比如:
- 某个函数被谁调用?调用栈是什么?
- 如何跟踪某个进程?如何跟踪一个命令,但是这个命令执行时间很短?
- 用户态的行为轨迹如何与内核中的 trace 联系到一起?
- 如何跟踪过滤多个进程?多个函数?
- 如何灵活控制 trace 的开关?
参考链接
https://www.cnblogs.com/wsg1100/p/17020703.html
二、Ftrace高级用法
1、基于ftrace的tracepoint跟踪
可基于 ftrace 跟踪内核静态跟踪点,可跟踪的完整列表可通过 available_events 查看。
#available_events 文件中包括全部可用于跟踪的静态跟踪点
$ cat available_events | grep openat
syscalls:sys_exit_openat2
syscalls:sys_enter_openat2
syscalls:sys_exit_openat
syscalls:sys_enter_openat
#在路径events/syscalls/sys_enter_openat 中查看sys_enter_openat跟踪点相关的选项
$ ls /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat
enable filter format hist id inject trigger
enable:是否启用跟踪
filter:跟踪过滤
format:跟踪点输出结果的格式
示例:使用tracepoint跟踪sys_openat
系统调用
$ echo 1 > events/syscalls/sys_enter_openat/enable
$ echo 1 > tracing_on
$ cat trace
# 跟踪结果输出如下
EasyMonitor-1043 [003] ..... 39762.091723: sys_openat(dfd: ffffff9c, filename: d19fa0, flags: 0, mode: 0)
cat-28619 [000] ..... 39762.170322: sys_openat(dfd: ffffff9c, filename: 7f5d0c69821b, flags: 80000, mode: 0)
cat-28619 [000] ..... 39762.170340: sys_openat(dfd: ffffff9c, filename: 7f5d0c6684b0, flags: 80000, mode: 0)
cat-28619 [000] ..... 39762.170599: sys_openat(dfd: ffffff9c, filename: 7f5d0c5e1570, flags: 80000, mode: 0)
cat-28619 [000] ..... 39762.170659: sys_openat(dfd: ffffff9c, filename: 7ffce744f70c, flags: 0, mode: 0)
systemd-oomd-846 [000] ..... 39762.191236: sys_openat(dfd: ffffff9c, filename: 561b721612c3, flags: 80000, mode: 0)
# 关闭
$ sudo echo 0 > events/syscalls/sys_enter_openat/enable
我们通过设置 events/syscalls/sys_enter_openat/enable
开启对于 sys_enter_openat
的跟踪,trace 文件中的跟踪记录格式与 sys_enter_openat/format
中的 print 的格式一致。
2、基于ftrace的kprobe 跟踪
kprobe 为内核提供的动态跟踪机制。kprobe机制允许跟踪函数的任意位置,可用于获取函数参数与结果返回值。
但是,使用 kprobe 机制跟踪函数须是 available_filter_functions
列表中的子集。
基于ftracek设置probe 跟踪的相关文件如下所示,其中部分文件为设置 kprobe 跟踪函数后,Ftrace 自动创建:
kprobe_events
设置 kprobe 跟踪的事件属性;
完整的设置格式如下,其中 GRP 用户可以直接定义,如果不设定默认为 kprobes
:
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] # 设置 probe 探测点 r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] # 函数地址的返回跟踪 -:[GRP/]EVENT # 删除跟踪
kprobes/<GRP>/<EVENT>/enabled
设置后动态生成,用于控制是否启用该内核函数的跟踪;
kprobes/<GRP>/<EVENT>/filter
设置后动态生成,kprobe 函数跟踪过滤器,与上述的跟踪点 fliter 类似;
kprobes/<GRP>/<EVENT>/format
设置后动态生成,kprobe 事件显示格式;
上面描述的有点抽象,我们举例说明下。
参数 | 定义 |
---|---|
GRP | 组名,如忽略会采用用缺省值kprobes |
EVENT | 事件名,如忽略会基于SYMBOL[+offs]或MEMADDR生成 |
MOD | 含有欲跟踪符号SYM的模块名 |
SYM[+offs] | 在 符号+偏移 处插入探针 |
SYM%return | 在 符号的返回地址 处插入探针 |
MEMADDR | 在地址MEMADDR处插入探针 |
MAXACTIVE | 可以同时探测的指定函数的最大实例数,否则为缺省值 0,见 Documentation/trace/kprobes.rst section 1.3.1 |
FETCHARGS | 探测点的参数,每个探测点可以有最多 128 个参数 |
跟踪函数入口参数
这里仍然以 __x64_sys_openat
函数为例,演示使用 kpboe 机制进行跟踪:
# p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]
# GRP=my_grp EVENT=x64_sys_openat
# SYM=__x64_sys_openat
# FETCHARGS = dfd=$arg1 flags=$arg3 mode=$arg4
# 参数解释如上所示
$ sudo echo 'p:my_grp/x64_sys_openat __x64_sys_openat dfd=$arg1 flags=$arg3 mode=$arg4' >> kprobe_events
# 启用x64_sys_openat事件的跟踪,并启用堆栈跟踪
$ sudo echo 1 > events/my_grp/x64_sys_openat/enable
$ sudo echo 1 > options/stacktrace
#获取跟踪结果
$ cat trace
cat-29679 [005] ..... 41534.009037: <stack trace>
=> __x64_sys_openat
=> entry_SYSCALL_64_after_hwframe
cat-29679 [005] ..... 41534.009053: x64_sys_openat: (__x64_sys_openat+0x0/0xa0) dfd=0xffffb53610443f58 flags=0xffffffffffffffff mode=0x0
cat-29679 [005] ..... 41534.009053: <stack trace>
=> __x64_sys_openat
=> entry_SYSCALL_64_after_hwframe
cat-29679 [005] ..... 41534.009234: x64_sys_openat: (__x64_sys_openat+0x0/0xa0) dfd=0xffffb53610443f58 flags=0xffffffffffffffff mode=0x0
cat-29679 [005] ..... 41534.009235: <stack trace>
=> __x64_sys_openat
=> entry_SYSCALL_64_after_hwframe
cat-29679 [005] ..... 41534.009291: x64_sys_openat: (__x64_sys_openat+0x0/0xa0) dfd=0xffffb53610443f58 flags=0xffffffffffffffff mode=0x0
cat-29679 [005] ..... 41534.009291: <stack trace>
# 关闭,注意需要先 echo 0 > enable 停止跟踪
# 然后再使用 "-:my_grp/x64_sys_openat" 停止,否则会正在使用或者忙的错误
$ sudo echo 0 > events/my_grp/x64_sys_openat/enable
$ sudo echo '-:my_grp/x64_sys_openat' >> kprobe_events
跟踪函数返回值
kprobe 可用于跟踪函数返回值,格式如下:
r[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]
例如:
$ sudo echo 'r:my_grp/x64_sys_openat __x64_sys_openat ret=$retval' >> kprobe_events
变量 $retval
参数表示函数返回值,其他的使用格式与 kprobe 类似。
# p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]
# GRP=my_grp_ret EVENT=x64_sys_openat
# SYM=__x64_sys_openat
# FETCHARGS = ret=$retval
# 参数解释如上所示
$ sudo echo 'r:my_grp_ret/x64_sys_openat __x64_sys_openat ret=$retval' >> kprobe_events
# 启用x64_sys_openat事件的跟踪,并启用堆栈跟踪
$ sudo echo 1 > events/my_grp_ret/x64_sys_openat/enable
#获取跟踪结果
$ cat trace
# 关闭,注意需要先 echo 0 > enable 停止跟踪
# 然后再使用 "-:my_grp_ret/x64_sys_openat" 停止,否则会正在使用或者忙的错误
$ sudo echo 0 > events/my_grp_ret/x64_sys_openat/enable
$ sudo echo '-:my_grp_ret/x64_sys_openat' >> kprobe_events
当同时开启函数入口跟踪和函数返回值跟踪时:
跟踪结果如下所示:
gsd-housekeepin-1697 [003] ..... 345.964115: x64_sys_openat: (__x64_sys_openat+0x0/0xa0) dfd=0xffffaf3444d0ff58 flags=0xffffffffffffffff mode=0x0
gsd-housekeepin-1697 [003] ..... 345.964115: <stack trace>
=> __x64_sys_openat
=> entry_SYSCALL_64_after_hwframe
gsd-housekeepin-1697 [003] ..... 345.964116: x64_sys_openat: (do_syscall_64+0x59/0x90 <- __x64_sys_openat) ret=0xa
gsd-housekeepin-1697 [003] ..... 345.964116: <stack trace>
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
同时能看到跟踪了函数的入口参数以及函数的返回值。
2、3 基于ftrace的uprobe 跟踪
uprobe 为用户空间的动态跟踪机制,格式和使用方式与 kprobe 的方式类似,但是由于是用户态程序跟踪需要指定跟踪的二进制文件和偏移量。
p[:[GRP/]EVENT]] PATH:OFFSET [FETCHARGS] # 跟踪函数入口
r[:[GRP/]EVENT]] PATH:OFFSET [FETCHARGS] # 跟踪函数返回值
-:[GRP/]EVENT] # 删除跟踪点
这里以跟踪 /bin/bash
二进制文件中的 readline()
函数为例:
$ readelf -s /bin/bash | grep -w readline
920: 00000000000d6070 208 FUNC GLOBAL DEFAULT 13 readline
$ echo 'p:my_grp/readline /bin/bash:0xd6070' >> uprobe_events
$ echo 1 > events/my_grp/readline/enable
$ 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
# | | | |||| | |
bash-14951 [003] .... 54570.055093: readline: (0xaaaab3ce6070)
#关闭跟踪
$ echo 0 > events/my_grp/readline/enable
$ echo '-:my_grp/readline' >> uprobe_events
uprobe 跟踪是跟踪用户态的函数,因此需要指定二进制文件+符号偏移量才能进行跟踪。不同系统中的二进制版本或者编译方式不同,会导致函数符号表的位置不同,因此需要跟踪前进行确认。
设置的所有基于ftrace的tracepoint,kprobe,uprobe跟踪点在linux系统重启后会丢失吗?
会的!
三、Ftrace 前端工具 trace-cmd 使用
1、安装
ubuntu系统
apt install trace-cmd
源代码编译安装
源代码下载地址: https://github.com/rostedt/trace-cmd
2、使用帮助手册
$ trace-cmd record -h
trace-cmd version 2.3.1
usage:
trace-cmd record [-v][-e event [-f filter]][-p plugin][-F][-d][-D][-o file] \
[-s usecs][-O option ][-l func][-g func][-n func] \
[-P pid][-N host:port][-t][-r prio][-b size][-B buf][command ...]
[-m max]
-e run command with event enabled #开启trace事件
-f filter for previous -e event
-p run command with plugin enabled #开启插件后运行命令
-F filter only on the given process //只抓取给定进程的 trace
-P trace the given pid like -F for the command
-c also trace the childen of -F
-T do a stacktrace on all events
-l filter function name //指定跟踪的函数
-g set graph function
-n do not trace function
-m max size per CPU in kilobytes
-M set CPU mask to trace
-v will negate all -e after it (disable those events)
-d disable function tracer when running
-D Full disable of function tracing (for all users)
-o data output file [default trace.dat]
-O option to enable (or disable)
-r real time priority to run the capture threads
-s sleep interval between recording (in usecs) [default: 1000]
-N host:port to connect to (see listen)
-t used with -N, forces use of tcp in live trace
-b change kernel buffersize (in kilobytes per CPU)
-B create sub buffer and folling events will be enabled here
-k do not reset the buffers after tracing.
-i do not fail if an event is not found
--func-stack perform a stack trace for function tracer //为函数跟踪器执行堆栈跟踪
(use with caution)
-p
:指定当前的 tracer-l
:指定跟踪的函数--func-stack
:记录被跟踪函数的调用栈
以下选项也很有用。
-n do not trace function: 指定不跟踪的函数
- 比如:
trace-cmd record -p function -l 'dev*' -n dev_attr_show
- 设置跟踪所有 dev 开头的函数,但是不跟踪
dev_attr_show
-g set graph function:指定 function_graph tracer 跟踪的 函数,类似 echo function_name > set_graph_function
-O option to enable (or disable): 设置 options,比如设置 options/func_stack_trace
可以用 -O func_stack_trace
,在 optoin 名称前加上 no
就是将 option 清 0
-P:设置跟踪的进程
3、 trace-cmd实战
3.1 Function Tracer
trace-cmd使用方式:
- trace-cmd record子命令将结果记录到本地文件中
- trace-cmd report子命令对结果进行提取
还以 dev_attr_show
为例,查看其调用栈的 trace-cmd 命令为:
trace-cmd record -p function -l dev_attr_show --func-stack
命令解释:
-p
:指定当前的 tracer,类似echo function > current_tracer
,可以是支持的 tracer 中的任意一个-l
:指定跟踪的函数,可以设置多个,类似echo function_name > set_ftrace_filter
--func-stack
:记录被跟踪函数的调用栈
提取 trace 结果的命令为:
trace-cmd report
先通过 record 子命令将结果记录到 trace.dat,再通过 report 命令进行结果提取。
3.2 Trace Event
命令示例:
- 跟踪 sched_switch :
trace-cmd record -e sched:sched_switch
- 跟踪某个 event 的同时记录调用栈:
trace-cmd record -e xxx -T
实例:
- 跟踪 sched_swtich events
顾名思义,sched_switch
可以监控系统内进程切换事件。
trace-cmd record -e sched_switch
- 查看跟踪结果
trace-cmd report
- 查看 sched_switch 消息格式
cat /sys/kernel/debug/tracing/events/sched/sched_switch/format
- report 结果的时候基于消息格式进行过滤
trace-cmd report -F "sched_switch: prev_comm == 'vmtoolsd'"
- 抓取过程中过滤使用
-f
,注意,-f
过滤参数中没有 event 名称
trace-cmd record -e sched_switch -f "prev_comm == 'containerd'"
使用过程中报错
trace-cmd: Permission denied can't create recorder
四、Ftrace 可视化工具kernelshark
apt install trace-cmd #KernelShark需要使用trace-cmd来收集跟踪数据
apt install kernelshark #安装KernelShark
sudo kernelshark
项目源代码的地址在https://github.com/yordan-karadzhov/kernel-shark
kernelshark是采用QT框架来实现的。
三个重要的文件
available_tracers
available_filter_functions
available_events
五、参考资料
网上不错的讲解ftrace的资料
https://github.com/freelancer-leon/notes/blob/master/kernel/trace/ftrace.md
https://mp.weixin.qq.com/s?src=11×tamp=1696238093&ver=4810&signature=9Ihtt3QXWFIXejdC59EET*BOJcON9OkcgR-cnbzN9Fe325i4r3J6K55i70IPPazIZV-dwB1-VSwX7WnwqYrPlHPf5QBr4qDwoD4EThRJOiiZ2L4Vo8k9bRA0eVZS4TMg&new=1
Linux内核跟踪:ftrace hook入门手册(上)
https://mp.weixin.qq.com/s/H8tpt7aWR6pvMAWi5-qVww
Linux内核跟踪:ftrace hook入门手册(下)