深入ftrace function原理

前面我们学习了ftrace的一些基本概念和用法,本章开始我们深入学习ftrace提供了哪些机制,根据之前的学习,我们知道了ftrace可用来快速排查以下问题

  • 特定的内核函数调用的频次(function)
  • 内核函数在被调用的过程中的调用栈(function + stack)
  • 内核函数调用的子函数流程(子调用栈)(function graph)
  • 使用trace event信息可以跟踪内核的流程

深入ftrace function原理

对于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,即不输出任何信息

深入ftrace function原理

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

深入ftrace function原理

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

深入ftrace function原理
  • 如果只想跟踪某个进程,可以使用set_ftrace_pid参数即可
  • 如果只想跟踪某个函数,可以使用set_ftrace_filter参数即可

如果开启动态配置选项,可以设置过滤函数,或指定跟踪函数,例如设置trace的过滤函数,即值跟踪blk_update_request

深入ftrace function原理

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

深入ftrace function原理

还可以用通配符”

#'*match*':匹配所有包含match的函数;
echo 'hrtimer_*' >> set_ftrace_filter  #过滤所有以"hrtimer_"开头的函数

还可以做基于模块的过滤:
例如,过滤 ext4 module 的 write* 函数:·

#&#x63A7;&#x5236;&#x8303;&#x5F0F;&#xFF1A;<function>:<command>:<parameter>
echo 'write*:mod:ext4' > set_ftrace_filter
</parameter></function>

深入ftrace function原理

感叹号用来移除某个函数,把多个组合过滤条件的某一个去掉:

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 语言实现一个非常简单程序进行简单验证:

深入ftrace function原理

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

深入ftrace function原理

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

深入ftrace function原理

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的寄存器

深入ftrace function原理

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

深入ftrace function原理

对于_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”指令

深入ftrace function原理

在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。

深入ftrace function原理

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

深入ftrace function原理

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

深入ftrace function原理

初始化时,遍历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;

    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);
        ftrace_update_code(mod, start_pg);
            ftrace_code_disable
                ftrace_make_nop(mod, rec, MCOUNT_ADDR);
                    ftrace_check_current_call(rec->ip, call);
                    ftrace_modify_code(pc, old, new, validate);
                        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;
    unsigned long       flags;
    struct dyn_arch_ftrace  arch;
};

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

深入ftrace function原理
  • 有44478个dyn_ftrace
  • 申请了174个4K pages

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

深入ftrace function原理

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

深入ftrace function原理

接下来,我们来看看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,就引入了这么一个宏定义:

深入ftrace function原理

其中有两个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

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

深入ftrace function原理

深入ftrace function原理

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

深入ftrace function原理

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

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

深入ftrace function原理

深入ftrace function原理

对于tracing_set_trace_write其实现

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

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

深入ftrace function原理

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

深入ftrace function原理

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

深入ftrace function原理

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

ftrace_startup_enable
    ftrace_run_update_code
        arch_ftrace_update_code
            ftrace_modify_all_code

首先更新tracer function

深入ftrace function原理

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

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

对于ftrace_call出的内容为

深入ftrace function原理

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

深入ftrace function原理

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

3 总结

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

  1. 编译阶段。通过编译选项 -pg -mrecord-mcount 在每个支持ftrace的函数中插入bl 0
  2. 链接阶段。会根据重定位段将bl 0
  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
  • 链接阶段。会根据重定位段将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/

Hooking Linux Kernel Functions, Part 2: How to Hook Functions with Ftrace

【原创】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

Original: https://blog.csdn.net/u012489236/article/details/127814059
Author: 奇小葩
Title: 深入ftrace function原理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/653757/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 初识商业智能

    一、什么是商业智能 商业智能(Business Intelligence,简称:BI),又称商业智慧或商务智能,指用现代数据仓库技术、线上分析处理技术、数据挖掘和数据展现技术进行数…

    人工智能 2023年7月17日
    099
  • 经验小波变换(EWT)理论基础

    项目说明 在信号分解领域,经验模态分解(EMD)十分经典,它基于信号特征自动地将信号分解为一组有限数目的 IMF 分量,在处理非线性和非平稳信号方面表现尤为出色,得到了广大学者的青…

    人工智能 2023年6月15日
    0135
  • vue+flask实现视频目标检测yolov5

    开始做这个之前,了解一些vue的基础,然后对flask完全不知道。所以特别感谢很多博主的文章。主要参考的是这篇文章:在WEB端部署YOLOv5目标检测(Flask+VUE),博主在…

    人工智能 2023年7月12日
    092
  • OpenCV调用USB摄像头的点滴

    一般项目中使用的相机多为工业相机,其厂商均配备了完善的二次开发SDK,含有丰富的接口。 但本人最近遇到了一款相机,最大的优点就是便宜(是同等工业相机价格的几分之一),遗憾的是没有任…

    人工智能 2023年7月19日
    063
  • 金融领域的知识图谱搭建简单实操(基于Neo4J)

    Tushare ID :475226 我国金融市场在改革开放之后取得了瞩目的成绩,并且随着信息技术的发展、金融市场的发展,金融机构都累积了非常庞大的数据量,包括了海量的交易内容和资…

    人工智能 2023年6月1日
    0106
  • 多分类-手写识别体

    1.分析数据集 数据集: 链接:https://pan.baidu.com/s/1YY9HuDqCSr3-CHWON3NdKg提取码:15eq mnist_train.csv 数据…

    人工智能 2023年7月8日
    085
  • QA-GNN: Reasoning with Language Models and Knowledge Graphsfor Question Answering

    题目:QA-GNN:使用语言模型和知识图进行问答推理作者:Michihiro Yasunaga、Hongyu Ren、Antoine Bosselut、Percy Liang、Ju…

    人工智能 2023年5月27日
    0108
  • pycharm安装教程(2022)与使用

    一、pycharm安装教程 1、进入官网下载pycharm官网下载地址:https://www.jetbrains.com/pycharm/download/#section=wi…

    人工智能 2023年7月5日
    077
  • 【Make YOLO Great Again】YOLOv1-v7全系列大解析(Neck篇)

    ; Rocky Ding 公众号:WeThinkIn 写在前面 【Make YOLO Great Again】栏目专注于从更实战,更深刻的角度解析YOLOv1-v7这个CV领域举足…

    人工智能 2023年7月9日
    0121
  • ClickHouse—函数汇总

    文章目录 ClickHouse—函数汇总 * 1. 算术函数 2. 比较函数 3. 取整函数 4. 逻辑函数 5. Hash函数 6. 类型转换函数 7 条件函数 8. UUID函…

    人工智能 2023年6月27日
    0106
  • Resnet 18网络模型

    残差网络:(Resnet) 残差块: 让我们聚焦于神经网络局部:如图左侧所示,假设我们的原始输入为x,而希望学出的理想映射为f(x)(作为上方激活函数的输入)。左图虚线框中的部分需…

    人工智能 2023年6月16日
    084
  • pandas.DataFrame.iloc函数总结

    pandas.DataFrame.iloc函数 property DataFrame.iloc:基于整数的用于选定所需数据的函数。 函数输入: 单个整数; 整数型list或者arr…

    人工智能 2023年7月8日
    098
  • 一文聊“图”,从图数据库到知识图谱

    作者 | 穆琼责编 | 晋兆雨头图 | 付费下载于视觉中国 随着知识图谱的发展,图数据库一词被越来越多的提到。那么到底什么是图数据库,为什么要用图数据库,如何去建设一个图数据库应用…

    人工智能 2023年6月10日
    0114
  • 卷积神经网络之卷积层理解(持续更新)

    目录 一、初识卷积层 二、图片卷积的过程(以步长为1,无填充情况为例) 三、卷积的填充 四、卷积的步长 五、卷积的输出大小计算 六、卷积的感受野 七、卷积层的深度 一、初识卷积层 …

    人工智能 2023年7月12日
    0132
  • 构建文本数据集(tokenize、vocab)

    根据李沐老师的课做的记录。 文本数据集可以将其看作一串单词序列或者字符序列。构建时一般有以下几个步骤。 文本清洗和读取 text_path = ‘./timemachine.txt…

    人工智能 2023年5月27日
    086
  • pytorch—基础篇(常用函数)

    说明:大部分关于张量的函数 torch.function()都可以使用 tensor.function() pytorch中张量的类型 tensor = tensor.half()…

    人工智能 2023年7月22日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球