深入ftrace function原理

前面我们学习了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

详细的见ftrace详解一文学会ftrace的基础用法

详细的使用方法见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.Smcount会判断函数指针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_entryftrace_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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值