深入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)
  • 放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"bl xxx"
  • 放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"b ftrace_graph_call

2.4 插桩初始化

在编译的时候调用recordmcount.pl搜集所有_mcount()函数的调用点,并且所有的调用点地址保存到section __mcount_loc。

在这里插入图片描述

最终内核的链接脚本include/asm-generic/vmlinux.lds.h将__mcount_loc段的内容放在.init.data段中,并且通过__start_mcount_loc和__stop_mcount_loc两个全局符号来访问。

在这里插入图片描述

最终会将所有的会链接到mcount_loc段中,在内核启动阶段会将bl _mcount替换成nop指令

在这里插入图片描述

初始化时,遍历section __mcount_loc的调用点地址,默认给所有“bl _mcount”替换成“nop”。在kernel/trace/ftrace.c中调用ftrace_init

void __init ftrace_init(void)
{
	extern unsigned long __start_mcount_loc[];
	extern unsigned long __stop_mcount_loc[];
	unsigned long count, flags;
	int ret;

	local_irq_save(flags);
	ret = ftrace_dyn_arch_init();
	local_irq_restore(flags);
	if (ret)
		goto failed;

	count = __stop_mcount_loc - __start_mcount_loc;
	if (!count) {
		pr_info("ftrace: No functions to be traced?\n");
		goto failed;
	}

	pr_info("ftrace: allocating %ld entries in %ld pages\n",
		count, count / ENTRIES_PER_PAGE + 1);

	last_ftrace_enabled = ftrace_enabled = 1;
    /* 遍历section __mcount_loc,处理其中保存的调用地址 */
	ret = ftrace_process_locs(NULL,
				  __start_mcount_loc,
				  __stop_mcount_loc);

	pr_info("ftrace: allocated %ld pages with %ld groups\n",
		ftrace_number_of_pages, ftrace_number_of_groups);

	set_ftrace_early_filters();

	return;
 failed:
	ftrace_disabled = 1;
}

在ftrace_process_locs函数中,内核为__start_mcount_loc和__stop_mcount_loc之间的每个地址都创建一个struct dyn_ftrace结构,其中ip记录着函数开始的stub地址,ftrace_code_disable函数会将这个地址的内容替换为nop指令,这样在没有trace时,系统的性能几乎没有影响。所以将bl _mcount替换成nop的函数调用过程如下:

ftrace_init
    ftrace_process_locs(NULL, __start_mcount_loc, __stop_mcount_loc);
        ftrace_allocate_pages(count); //分配dyn_ftrace表占据的空间
        ftrace_update_code(mod, start_pg);//对表中的每一项地址进行code修改
            ftrace_code_disable
                ftrace_make_nop(mod, rec, MCOUNT_ADDR);//将bl _mcount对应的指令码替换掉
                    ftrace_check_current_call(rec->ip, call);//校验地址中的内容的合法性
                    ftrace_modify_code(pc, old, new, validate);//这里的new就是nop对应的指令码,old是根据MCOUNT_ADDR计算出来的bl addr的指令码
                        aarch64_insn_patch_text_nosync
                            aarch64_insn_write(tp, insn);//更改指令码
[    0.000000]       .text : 0xffffff8008080000 - 0xffffff8008930000   (  8896 KB)
[    0.000000]     .rodata : 0xffffff8008930000 - 0xffffff8008c50000   (  3200 KB)
[    0.000000]       .init : 0xffffff8008c50000 - 0xffffff8008d50000   (  1024 KB)
[    0.000000]       .data : 0xffffff8008d50000 - 0xffffff8008eb2008   (  1417 KB)
[    0.000000]        .bss : 0xffffff8008eb2008 - 0xffffff8009a49e00   ( 11872 KB)

//这个替换大概用了14ms
[3.281092 0.001971] [    0.000000] ---ftrace_init start---
[3.281930 0.000838] [    0.000000] ftrace: allocating 30444 entries in 119 pages
[3.295482 0.013552] [    0.000000] ---ftrace_init end---

//在ftrace_replace_code中做计数,总共替换了31726个函数。
[  136.262494] --ftrace_replace_code---count:31726

对于ftrace_make_nop其实现如下,找到旧的,然后以新的nop作为替换就可以了

ftrace_make_nop(mod, rec, MCOUNT_ADDR)
    old = ftrace_call_replace(ip, MCOUNT_ADDR)
        text_gen_insn(CALL_INSN_OPCODE, (void *)ip, (void *)MCOUNT_ADDR);
    new = ftrace_nop_replace
    ftrace_modify_code_direct(ip, old, new)
        ftrace_verify_code(ip, old)
        text_poke_early(ip, new, MCOUNT_INSN_SIZE)

这是在不使能的情况下,如果要使能,内核是如何做的呢?首先我们需要考虑以下几点

  • 需要确定想要trace的函数的插桩点位置-------这个已经保存,遍历section __mcount_loc的调用点地址
  • 我们需要记录某个trace点的状态,例如我们之前的schedule,我们在有些情况是使能的,某些情况是不使能,那么我们就需要记录这个状态

所以内核使用了一个新的结构来完成这个工作, 每个“bl _mcount”的插桩点ip对应一个每个插桩点对应一个dyn_ftrace结构

struct dyn_ftrace {
	unsigned long		ip;         /* address of mcount call-site */
	unsigned long		flags;      /* 这个用于存放插桩点的相关状态 */
	struct dyn_arch_ftrace	arch;   /* 大部分架构为空 */
};

内核使用ftrace_pages来保存dyn_ftrace的数据,将_mcount_loc数据迁移到ftrace_pages后,将数据释放掉,ftrace_pages中的数据是有序的,便于查找,通过dmes可以看到ftrace_pages占用的内存

在这里插入图片描述

  • 有44478个dyn_ftrace
  • 申请了174个4K pages

ftrace_pages根据mcount_loc中数据,填好数据如下:

在这里插入图片描述

我们通过用户态获取avilable_filter_functions的数据,其实也是通过ftrace_pages中经过转换得到对应的函数,顺序也是跟ftrace_pages的存储顺序相同

在这里插入图片描述

接下来,我们来看看flag这个标志位

enum {
	FTRACE_FL_ENABLED	= (1UL << 31),
	FTRACE_FL_REGS		= (1UL << 30),
	FTRACE_FL_REGS_EN	= (1UL << 29),
	FTRACE_FL_TRAMP		= (1UL << 28),
	FTRACE_FL_TRAMP_EN	= (1UL << 27),
	FTRACE_FL_IPMODIFY	= (1UL << 26),
	FTRACE_FL_DISABLED	= (1UL << 25),
	FTRACE_FL_DIRECT	= (1UL << 24),
	FTRACE_FL_DIRECT_EN	= (1UL << 23),
};
  • 低22 bit用来表示引用计数,如果有ftrace_ops会操作该ip,引用计数会增加1,例如我们的function trace已经trace这个函数,这个时候其他的用户,例如perf也想用这个trace框架,这个低22位就存放这个计数
  • ENABLED - the function is being traced
  • REGS - the record wants the function to save regs
  • REGS_EN - the function is set up to save regs.
  • IPMODIFY - the record allows for the IP address to be changed.
  • DISABLED - the record is not ready to be touched yet
  • DIRECT - there is a direct function to call

对于其过程如下:

ftrace_pages_start
      |
      v
    ftrace_page
    +-----------------------------+
    |index                        |
    |size                         |
    |    (int)                    |     array of dyn_ftrace
    |records                      |     +----------+----------+     +----------+----------+
    |    (struct dyn_ftrace*)     |---->|ip        |          | ... |          |          |
    |                             |     |flags     |          |     |          |          |
    |                             |     |arch      |          |     |          |          |
    |next                         |     +----------+----------+     +----------+----------+
    |    (struct ftrace_page*)    |
    +-----------------------------+
      |
      |
      v
    ftrace_page
    +-----------------------------+
    |index                        |
    |size                         |
    |    (int)                    |     array of dyn_ftrace
    |records                      |     +----------+----------+     +----------+----------+
    |    (struct dyn_ftrace*)     |---->|ip        |          | ... |          |          |
    |                             |     |flags     |          |     |          |          |
    |next                         |     |arch      |          |     |          |          |
    |    (struct ftrace_page*)    |     +----------+----------+     +----------+----------+
    +-----------------------------+
      |
      |
      v
    ftrace_page
    +-----------------------------+
    |index                        |
    |size                         |
    |    (int)                    |     array of dyn_ftrace
    |records                      |     +----------+----------+     +----------+----------+
    |    (struct dyn_ftrace*)     |---->|ip        |          | ... |          |          |
    |                             |     |flags     |          |     |          |          |
    |next                         |     |arch      |          |     |          |          |
    |    (struct ftrace_page*)    |     +----------+----------+     +----------+----------+
    +-----------------------------+

从此所有的探针就都在ftrace_pages_start为起始的一张表中。为了遍历这张特殊的表,访问到其中的每一个entry,就引入了这么一个宏定义:

在这里插入图片描述

其中有两个for循环,一个是遍历ftrace_pages_start为首的链表,一个是编译每个ftrace_page的records。

2.4 tracer函数探针动态配置

当系统完成初始化后,所有的_mcount_loc中的地址处的内容均被修改成nop指令,如果需要对函数进行跟踪记录,则需要进一步将nop指令修改为函数探针的跳转指令。

ftrace框架中提供了多种的函数探针,通过函数register_tracer进行注册,并通过过/sys/kernel/debug/tracing/current_tracer节点来进行配置。根据该节点的操作函数可知,由函数tracing_set_trace_write对函数探针进行配置:

tracer可以通过register_tracer()进行注册。以function tracer为例,kernel/trace/trace_functions.c

static struct tracer function_trace __tracer_data =
{
	.name		= "function",
	.init		= function_trace_init,
	.reset		= function_trace_reset,
	.start		= function_trace_start,
	.flags		= &func_flags,
	.set_flag	= func_set_flag,
	.allow_instances = true,
#ifdef CONFIG_FTRACE_SELFTEST
	.selftest	= trace_selftest_startup_function,
#endif
};

__init int init_function_trace(void)
{
	init_func_cmd_traceon();
	return register_tracer(&function_trace);
}

初始化完成之后,我们关心的是怎么样能够再次打开探针。首先我们得找到这种操作的入口,比如使用”echo xxx > set_ftrace_filter“命令来配置function

首先我们找到这个文件的定义函数:

在这里插入图片描述

在这里插入图片描述

顺着这个看,中主要的就是match_recors,这个通过do_for_each_ftrace_rec遍历整个探针的记录,根据index找到究竟是要操作那个探针,然后通过enter_record()这个探针添加到ftrace_hash这个哈希表中

在这里插入图片描述

这样我们就知道了修改时,需要修改哪些探针地址,但是我们还没有真正的去修改这些代码,这个就是通过我们去tracer使能完成。

我们知道可以使用“echo function > current_tracer”命令来使能或者切换tracer,来选择不同的tracer,输出不同的记录,。来具体看看其代码实现:
在这里插入图片描述

在这里插入图片描述

对于tracing_set_trace_write其实现

在这里插入图片描述

  • 当判断当前设置的tracer是否被注册过,race_types是一个全局变量,当使能了某个tracer时,调用register_tracer将tracer挂接到trace_types的链表中
  • 需要设置成为的tracer和当前正在使用中的tracer不同时,先要调用正在使用的tracer的reset函数
  • 调用需要设置成为的tracer的init函数

以function tracer为例,对tracer的init过程进行解析:

在这里插入图片描述

对于function tracer的开启过程,如下图

在这里插入图片描述

对于__register_ftrace_function对当前的function tracer进行注册,将其挂到tracer链表中

在这里插入图片描述

最终会调用ftrace_startup_enable替换了不同的函数,对于ARM64其调用流程如下:

ftrace_startup_enable
    ftrace_run_update_code
        arch_ftrace_update_code
            ftrace_modify_all_code

首先更新tracer function

在这里插入图片描述

到这里,我们基本能看出一些端倪。

  • 找到旧的指令 – old
  • 找到新的指令 – new
  • 用ftrace_modify_code()将old替换成new

对于ftrace_call出的内容为

在这里插入图片描述

ftrace_replace_code替换所有_mcount_loc中地址的内容

在这里插入图片描述

对于每一个表项,都对其进行判断,对于需要跟踪的函数,调用__ftrace_replace_code将nop替换为ftrace_caller,对于不需要跟踪的函数,则不做任何操作,是否需要根据FTRACE_FL_DISABLED 标志来判断,该标志由set_ftrace_filter来添加。

3 总结

到此我们已经分析完了ftrace的各个阶段的行为,以及钩子函数的替换过程,基本上包含如下过程:

  1. 编译阶段。通过编译选项 -pg -mrecord-mcount 在每个支持ftrace的函数中插入bl 0 <_mcount>指令

  2. 链接阶段。会根据重定位段将bl 0 <_mcount>指令地址重定位为_mcount函数地址。

  3. 运行阶段

    (1)ftrace_init:会将可trace函数中的bl _mcount替换为nop指令;调用 ftrace_process_locs 把所有 mcount 调用点替换为 nop 指令:ftrace_make_nop()

    (2)执行echo blk_update_request >set_ftrace_filter:会使能blk_update_request的钩子函数替换标记(nop替换为ftrace_caller);

    (3)执行echofunction > current_tracer:触发两步替换:调用 ftrace_run_update_code,替换回 mcount 调用点:ftrace_make_call()

    ​ 第一步,ftrace_caller中ftrace_call被替换为ftrace_ops_no_ops;

    第二步,blk_update_request中的nop被替换为ftrace_caller。ftrace_caller最终会调用到function_trace_call,它会记录函数调用堆栈信息,并将结果写入 ring buffer,用户可以通过/sys/kernel/debug/tracing/trace文件读取该 ring buffer 中的内容。

到此我们已经分析完了ftrace的各个阶段的行为,以及钩子函数的替换过程,基本上包含如下过程:-

  • 编译阶段。通过编译选项 -pg -mrecord-mcount 在每个支持ftrace的函数中插入bl 0 <_mcount>指令
  • 链接阶段。会根据重定位段将bl 0 <mcount>指令地址重定位为mcount函数地址。
  • 运行阶段

(1)ftrace_init:会将可trace函数中的bl _mcount替换为nop指令;调用 ftrace_process_locs 把所有 mcount 调用点替换为 nop 指令:ftrace_make_nop()

compile time     __fentry__
                             +------------->+---------------+
                             |              |retq           |
                             |              |               |
          kernel._text       |              +---------------+
          +--------------+   |
          |              |   |
          |              |   |boot up
    ip -> |           ---|---+-------------> = nop
          |              |   |
          |              |   |
          +--------------+   |
                             |
                             |
                             |enabled         ftrace_caller
                             +------------->+---------------+
                                            |               |
                                            |ftrace_call    |
                                            |               |
                                            +---------------+

(2)执行echo blk_update_request >set_ftrace_filter:会使能blk_update_request的钩子函数替换标记(nop替换为ftrace_caller);

(3)执行echofunction > current_tracer:触发两步替换:调用 ftrace_run_update_code,替换回 mcount 调用点:ftrace_make_call()

4 参考文档

https://www.ebpf.top/post/ftrace_tools/

https://blog.arstercz.com/introduction_to_linux_dynamic_tracing/

https://www.apriorit.com/dev-blog/546-hooking-linux-functions-2

【原创】Kernel调试追踪技术之 Ftrace on ARM64

Kernel Recipes 2019 - ftrace: Where modifying a running kernel all started

Kernel Recipes 2017 - Understanding the Linux Kernel via Ftrace - Steven Rostedt

LF Live Mentorship Session: Tracing with Ftrace: Critical Tooling for Linux Development

openEuler kernel技术分享-第13期-ftrace框架及指令修改机制_哔哩哔哩_bilibili

一道思考题所引起动态跟踪 ‘学案’

https://www.ebpf.top/post/ftrace_kernel_dynamic/

https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/04-ftrace_internal

https://blog.linuxplumbersconf.org/2014/ocw/system/presentations/1773/original/ftrace-kernel-hooks-2014.pdf

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ftrace和ptrace是两个不同的工具,它们在功能和用途上有所区别。 引用中提到的ftraceLinux 内核的一个内建跟踪工具,用于跟踪和分析内核函数调用、上下文切换、延迟和性能问题等。它可以通过配置内核和 debugfs 来使用,并包含多个跟踪器,可以方便地跟踪不同类型的信息。 而引用中提到的ptrace是一个系统调用,用于在用户空间中跟踪和控制进程的执行。通过ptrace,用户可以监视和修改目标进程的内存、寄存器和执行状态,实现调试和跟踪进程的功能。 因此,ftrace主要用于内核级别的跟踪和性能分析,而ptrace主要用于用户空间进程的调试和跟踪。它们各自具有不同的功能和应用场景,但都能提供有助于问题排查和性能优化的信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Linux内核调试方法总结之strace ,ltrace, ptrace, ftrace, sysrq](https://blog.csdn.net/zmjames2000/article/details/88410484)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Linux内核学习(十):内核追踪必备技能--ftrace](https://blog.csdn.net/weixin_45264425/article/details/125955998)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值