前面我们学习了ftrace的一些基本概念和用法,本章开始我们深入学习ftrace提供了哪些机制,根据之前的学习,我们知道了ftrace可用来快速排查以下问题
- 特定的内核函数调用的频次(function)
- 内核函数在被调用的过程中的调用栈(function + stack)
- 内核函数调用的子函数流程(子调用栈)(function graph)
- 使用trace event信息可以跟踪内核的流程

对于ftrace跟踪工具由性能分析器(profiler)和跟踪器(tracer)两部分组成:
-
性能分析器: 用来提供统计和直方图数据(需要 CONFIG_FUNCTION_PROFILER=y),可以提供函数性能分析或直方图
-
**跟踪器:**提供跟踪事件的详情
- 函数跟踪(function):追踪所有的内核函数
- 函数调用关系(function_grap):与function比较类似,但它除了探测函数的入口还探测函数的出口,它可以画出一个图形化的函数调用,类似于c源代码风格。
- 跟踪点(tracepoint):是内核提供的静态跟踪点,为稳定的跟踪点,需要研发人员代码编写,数量有限
- kprobe/kretprobe,为内核提供动态跟踪机制,/proc/kallsyms中的函数几乎都可以被用于跟踪,但是内核函数可能随着版本演进而发生变化,为非稳定跟踪机制,数量比较多
- uprobe
本系列用主要以what->why->how为原则,对上面几个跟踪问题进行详细的学习,本章主要是function和function_grap为开篇,本文主要是以ARM64为主。
1 function 使用方法
首先我们来回顾下,对于function也是满足三步法,设置tracer类型,设置tracer参数,使能tracer
默认情况下,当前nop,即不输出任何信息

对于function,可以输入以下的命令,function默认会记录当前运行过程中的所有函数,可以看到上述一个普通的系统,function tracer的插桩点为51505个

这么庞大的数量的插桩点,如果独立控制会消耗大量的内存,同时会导致很大的开销。

-
如果只想跟踪某个进程,可以使用set_ftrace_pid参数即可
-
如果只想跟踪某个函数,可以使用set_ftrace_filter参数即可
如果开启动态配置选项,可以设置过滤函数,或指定跟踪函数,例如设置trace的过滤函数,即值跟踪blk_update_request

设置不跟踪指定函数,我们以调度器中使用很频繁的update_rq_clock为例

还可以用通配符”
#'*match*':匹配所有包含match的函数;
echo 'hrtimer_*' >> set_ftrace_filter #过滤所有以"hrtimer_"开头的函数
还可以做基于模块的过滤:
例如,过滤 ext4 module 的 write* 函数:·
#控制范式:<function>:<command>:<parameter>
echo 'write*:mod:ext4' > set_ftrace_filter

感叹号用来移除某个函数,把多个组合过滤条件的某一个去掉:
echo '!ip_rcv' >> set_ftrace_filter
遇到 __schedule_bug 函数后关闭 trace
# echo '__schedule_bug:traceoff' > set_ftrace_filter
详细的使用方法见Documentation/ftrace.txt
2 Function Trace的实现
2.1 基础知识
我们用 C 语言实现一个非常简单程序进行简单验证:

在默认参数编译后的代码如下gcc -o hello hello.c ,objdump -S hello可见函数头部没有特殊定义。

使用 -pg 参数编译后,我们可以看到在函数头部增加了对 mcount 函数的调用,这种机制常用用于运行程序性能分析:gcc -pg -o hello.pg hello.c

function是通过在编译阶段在函数入口插入空指令,运行是对于制定的函数,将空指令替换为要执行的钩子回调,在钩子回调函数中记录函数调用关系,并写入ftrace的ring buffer。
对于ftrace实现是基于编译器选项-pg和-mfentry,这个内核选项在每个函数的开头插入一个特殊跟踪函数的调用–mcount()或fentry()。这部分依赖于处理器架构,文档 Documentation/trace/ftrace-design.txt 中详细介绍了kernel中实现一个处理器架构支持mcount需要的接口。
如果没有使用ftrace,它几乎不会影响系统,因为内核知道调用mcount()或fentry()的位置,并在早期阶段将机器码替换为nop,当linux内核跟踪打开时,ftrace调用会被添加到必要的函数中。
mcount在不同处理器的gcc实现名字可能略有差异,可能是mcount,_mcount或者__mcount,可以通过下面命令查看,arm64中是_mcount本文统一使用mcount。
echo 'main(){}' | gcc -x c -S -o - - -pg | grep mcount
call mcount
ARM64的实现主要有下面三个文件
arch/arm64/kernel/entry-ftrace.S // mcount的核心实现
arch/arm64/kernel/ftrace.c // CONFIG_DYNAMIC_FTRACE 的支持接口
arch/arm64/include/asm/ftrace.h // 声明和定义arm64为ftrace核心模块提供的接口
mcount的主要任务是根据是否打开了ftrace,跳转到对应的注册函数中,做进步一处理。
2.2 静态mcount实现
Kernel中打开CONFIG_FUNCTION_TRACER 后,会增加-pg编译选项,这样在每个函数入口处都会插入bl mcount跳转指令,函数运行时会进入mcount函数。mcount会判断函数指针ftrace_trace_function是否被注册,默认注册的是空函数ftrace_stub,只有打开function tracer后才会注册具体的处理函数ftrace_trace_function。在根目录的Makefile文件
# The arch Makefiles can override CC_FLAGS_FTRACE. We may also append it later.
ifdef CONFIG_FUNCTION_TRACER
CC_FLAGS_FTRACE := -pg
endif
对于tracer自身而言,是不需要-pg选项的,因此我们可以看到在kernel/tracing/Makefile中将-pg从该模块的CFLAGS中剔除了kernel/tracing/Makefile
# Do not instrument the tracer itself:
ccflags-remove-$(CONFIG_FUNCTION_TRACER) += $(CC_FLAGS_FTRACE)
所以对于不希望被 ftrace 跟踪的模块也可以如法炮制。
- 用
notrace标记的 函数 不会被跟踪。 - 用
CFLAGS_REMOVE_file.o = -pg禁止对 某个文件用-pg编译
对于内核中使用说明,当gcc使用-pg,也就是在函数的添加了这么两句指令
/*
* Gcc with -pg will put the following code in the beginning of each function:
* mov x0, x30
* bl _mcount
* [function's body ...]
* "bl _mcount" may be replaced to "bl ftrace_caller" or NOP if dynamic
* ftrace is enabled.
* /
为什么要有mov x0,x30,是因为bl需要一个参数,而x30是lr的寄存器

我们已内核的schedule函数为例,编译好后

对于_mcount的实现,代码在arch/arm64/kernel/entry-ftrace.S,mcount会判断函数指针ftrace_trace_function是否被注册,默认注册的是空函数ftrace_stub,只有打开function tracer后才会注册具体的处理函数ftrace_trace_function。
#ifndef CONFIG_DYNAMIC_FTRACE
/*
* void _mcount(unsigned long return_address)
* @return_address: return address to instrumented function
*
* This function makes calls, if enabled, to:
* - tracer function to probe instrumented function's entry,
* - ftrace_graph_caller to set up an exit hook
*/
SYM_FUNC_START(_mcount)
mcount_enter
ldr_l x2, ftrace_trace_function
adr x0, ftrace_stub
cmp x0, x2 // if (ftrace_trace_function
b.eq skip_ftrace_call // != ftrace_stub) {
mcount_get_pc x0 // function's pc
mcount_get_lr x1 // function's lr (= parent's pc)
blr x2 // (*ftrace_trace_function)(pc, lr);
skip_ftrace_call: // }
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
ldr_l x2, ftrace_graph_return
cmp x0, x2 // if ((ftrace_graph_return
b.ne ftrace_graph_caller // != ftrace_stub)
ldr_l x2, ftrace_graph_entry // || (ftrace_graph_entry
adr_l x0, ftrace_graph_entry_stub // != ftrace_graph_entry_stub))
cmp x0, x2
b.ne ftrace_graph_caller // ftrace_graph_caller();
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
mcount_exit
SYM_FUNC_END(_mcount)
EXPORT_SYMBOL(_mcount)
NOKPROBE(_mcount)
- 这种是静态的ftrace,ftrace是有一个全局的ftrace_trace_function变量,每当我们设置了一个trace,这个ftrace_trace_function就指向我们设置的trace的回调函数,如果ftrace_trace_function不等于默认的桩函数ftrace_stub,则调用调用function tracer的回调函数:ftrace_trace_function()
- 另外,如果我们没有配置任何的trace,如果配置了
CONFIG_FUNCTION_GRAPH_TRACER,就不再是注册ftrace_trace_function,而是注册ftrace_graph_entry和ftrace_graph_return。function graph tracer是function trace的增强版,可以打印出完整的函数调用关系。
很明显,这样做有很大的副作用,其主要表现为
- 一旦使能了function trace,会对kernel中所有的函数(inline/notrace修饰除外)进行trace,性能开销会很大,并且trace日志太多,会冲刷感兴趣的日志
- 不使能的时,也会有_mcount的调用,这个开销也会很大
2.3 dynamic ftrace
static ftrace一旦使能,对kernel中所有的函数(除开notrace、online、其他特殊函数)进行插桩,这带来的性能开销是惊人的,有可能导致人们弃用ftrace功能。
为了解决这个问题,内核开发者推出了dynamic ftrace,因为实际上调试者一般不需要对所有函数进行跟踪,只会对感兴趣的一部分函数进行追踪。
- dynamic ftrace把不需要追踪的函数入口处指令“bl _mcount”替换成“nop”,这样基本对性能无影响
- 对需要追踪的函数替换入口处的“bl _mcount”为需要调用的函数
所有需要对插桩点进行收集和动态管理,根据编译时“scripts/recordmcount.pl”脚本记录的所有函数入口处插桩位置的“bl _mcount”,将其替换成“nop”指令

在trace enable的时候,把需要跟踪的函数的插桩位置"nop"替换成"bl ftrace_caller"
SYM_FUNC_START(ftrace_caller)
mcount_enter
mcount_get_pc0 x0 // function's pc
mcount_get_lr x1 // function's lr
SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL) // tracer(pc, lr);
nop // This will be replaced with "bl xxx"
// where xxx can be any kind of tracer.
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
SYM_INNER_LABEL(ftrace_graph_call, SYM_L_GLOBAL) // ftrace_graph_caller();
nop // If enabled, this will be replaced
// "b ftrace_graph_caller"
#endif
mcount_exit
SYM_FUNC_END(ftrace_caller)

最低0.47元/天 解锁文章
1048

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



