园林工建设有限公司网站毕业设计做网站用什么软件

当前位置: 首页 > news >正文

园林工建设有限公司网站,毕业设计做网站用什么软件,h5入口,上海专业制作网站》内核新视界文章汇总《 文章目录 ftrace 原理分析1 简介2 ftrace 的编译器支持2.1 HAVE_FUNCTION_TRACER 选项对 ftrace 的支持2.2 HAVE_DYNAMIC_FTRACE 选项对动态 ftrace 的支持 3 ftrace 的初始化4 function trace 流程5 总结 ftrace 原理分析 1 简介 ftrace 是一个内核…》内核新视界文章汇总《 文章目录 ftrace 原理分析1 简介2 ftrace 的编译器支持2.1 HAVE_FUNCTION_TRACER 选项对 ftrace 的支持2.2 HAVE_DYNAMIC_FTRACE 选项对动态 ftrace 的支持 3 ftrace 的初始化4 function trace 流程5 总结 ftrace 原理分析 1 简介 ftrace 是一个内核内部跟踪程序用于帮助系统开发者和设计者观察内核内部正在发生的事情。它可以用于调试和分析用户空间之外的延迟和性能问题。 ftrace 的本意是指function tracer这是因为它最开始主要是为了跟踪与采集内核运行时函数的调用与执行情况。经过不断发展ftrace逐渐提供了各类跟踪功能。例如查看进程上下文切换的相关信息、系统中断被禁用的时间以及高优先级进程从被唤醒到被系统调度执行期间的最大延迟时间等等。这些功能可以很好的辅助内核开发和研究人员对内核进行调试 并及时发现内核中的各类问题。另外ftrace也具有很好的拓展性 它提供了一些简洁易用的接口来允许开发者以插件的形式添加和使用更多种类的跟踪器tracer 正因如此 其逐渐发展成为了一个内核跟踪框架。一些常见的ftrace跟踪器如下 跟踪器相关功能描述function函数调用跟踪程序来跟踪所有内核函数。function_graph与函数跟踪程序相似不同之处在于函数跟踪程序在函数的入口探测函数而函数图跟踪程序在函数的入口和出口都进行跟踪。然后它提供了绘制函数调用图的能力类似于C源代码。blk阻塞式输入输出跟踪器 跟踪记录Block I/O 相关信息irqsoff跟踪禁用中断的区域并以最长的最大延迟保存跟踪。看tracing_max_latency文件。当记录新的max时它将替换旧的跟踪值。最好在启用latency-format选项的情况下查看此跟踪这在选择跟踪程序时自动设置。preemptoff类似于irqsoff但是跟踪和记录抢占被禁用的时间量preemptirqsoff类似于irqsoff和preemptoff但是跟踪和记录irqs和/或抢占被禁用的最大时间。wakeup跟踪和记录在最高优先级任务被唤醒后实际调度它所需要的最大延迟。按照一般开发人员的预期跟踪所有任务。wakeup_rt跟踪和记录RT任务所需要的最大延迟。这对于那些对RT任务的唤醒时间感兴趣的人很有用。wakeup_dl跟踪和记录唤醒SCHED_DEADLINE任务所需的最大延迟(与“wakeup”和“wakeup_rt”一样)。mmiotrace一种用于跟踪二进制模块的特殊跟踪程序。它将跟踪一个模块对硬件的所有调用。它也从I/O中写入和读取所有内容。branch这个跟踪程序可以在跟踪内核中可likely/unlikley的调用时配置。它将跟踪何时命中可能和不可能的分支以及它的预测是否正确。 同样的Ftrace还有个最常见的用途是 event 跟踪。内核有数百个静态事件点可以通过tracefs启用这些事件点以便查看内核的某些部分正在发生什么。 由于 ftrace 是一个大的框架这里基于 function trace 进行分析。 2 ftrace 的编译器支持 2.1 HAVE_FUNCTION_TRACER 选项对 ftrace 的支持 首先对应 arch 要使用 ftrace 需要满足下面两个基础特性 STACKTRACE_SUPPORT - 实现save_stack_trace()TRACE_IRQFLAGS_SUPPORT - 实现include/asm/irqflags.h 当 arch 支持 ftrace 时需要在 Kconfig 中选中 HAVE_FUNCTION_TRACER 选项该选项将会激活 ftrace 功能。 该功能将会在编译阶段为代码添加 -pg选项该选项将会为每个函数开头生成mcount/mcount函数这对于用户空间不是难事libc 库定义了 mcount函数来完成相关工作。而对于内核则需要架构代码去实现mcount和ftrace_stub函数。不同架构可能生成的函数名不是 mcount而是_mcount/mcount这取决于具体架构。 如下 // x86有 -pg // echo main(){} | gcc -x c -S -o - - -pg.text.globl main.type main, function main: .LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6 1: call mcountGOTPCREL(%rip) // 调用 mcountmovl \(0, %eaxpopq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc ... // x86没有 -pg // echo main(){} | gcc -x c -S -o - -.text.globl main.type main, function main: .LFB0:.cfi_startprocendbr64pushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6 // 没有 mcount 相关函数生成调用movl \)0, %eaxpopq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc .LFE0: …// aarch64有 -pg // echo main(){} | aarch64-linux-gnu-gcc -x c -S -o - - -pg.text.global _mcount.align 2.global main.type main, %function main: .LFB0:.cfi_startprocstp x29, x30, [sp, -16]!.cfi_def_cfa_offset 16.cfi_offset 29, -16.cfi_offset 30, -8mov x29, spmov x0, x30mov x30, x0hint 7 // xpaclrimov x0, x30bl _mcount // 生成了 _mcountmov w0, 0ldp x29, x30, [sp], 16.cfi_restore 30.cfi_restore 29.cfi_def_cfa_offset 0ret.cfi_endproc .LFE0:ftrace 对于 mcount 的内部调用进行了以便满足 ftrace 的要求。下面描述的是一个正确的mcount 应该做的事 mcount应该检查函数指针ftrace_trace_function看看它是否被设置为 ftrace_stub如果是那么无事可做直接返回如果不是则进行调用ftrace_trace_function第一个参数是 frompc第二个参数时 selfpc。例如foo() 调用 bar()当 bar() 调用 mcount() 时 frompc - 地址 bar() 将被用来返回到 foo()selfpc - 地址 bar() mcount() 大小校正 // 理论流程 void ftrace_stub(void) {return; }void mcount(void) {/ save any bare state needed in order to do initial checking */extern void (ftrace_trace_function)(unsigned long, unsigned long);if (ftrace_trace_function ! ftrace_stub)goto do_trace;/ restore any bare state /return;do_trace:/ save all state needed by the ABI (see paragraph above) /unsigned long frompc …;unsigned long selfpc return address - MCOUNT_INSN_SIZE;ftrace_trace_function(frompc, selfpc);/ restore all state needed by the ABI */ }// arm64 实现 ENTRY(_mcount)mcount_enterldr_l x2, ftrace_trace_functionadr x0, ftrace_stubcmp x0, x2 // if (ftrace_trace_functionb.eq skip_ftrace_call // ! ftrace_stub) {mcount_get_pc x0 // functions pcmcount_get_lr x1 // functions lr ( parents pc)blr x2 // (ftrace_trace_function)(pc, lr);skip_ftrace_call: // } #ifdef CONFIG_FUNCTION_GRAPH_TRACERldr_l x2, ftrace_graph_returncmp x0, x2 // if ((ftrace_graph_returnb.ne ftrace_graph_caller // ! ftrace_stub)ldr_l x2, ftrace_graph_entry // || (ftrace_graph_entryadr_l x0, ftrace_graph_entry_stub // ! ftrace_graph_entry_stub))cmp x0, x2b.ne ftrace_graph_caller // ftrace_graph_caller(); #endif / CONFIG_FUNCTION_GRAPH_TRACER */mcount_exit ENDPROC(_mcount) EXPORT_SYMBOL(_mcount) NOKPROBE(_mcount)基于HAVE_FUNCTION_TRACER我们还可以选择HAVE_FUNCTION_GRAPH_TRACER它会开启上述的CONFIG_FUNCTION_GRAPH_TRACER选项以便于可以根据栈绘制调用表关系图未仔细研究该部分。 同样的在 CONFIG_FUNCTION_GRAPH_TRACER下检查 ftrace_graph_return函数指针如果没有设置特定于ftrace_graph_entry和ftrace_graph_entry_stub函数则调用特定于架构的ftrace_graph_caller该函数反过来调用特定于架构的 prepare_ftrace_returnprepare_ftrace_return参数与 ftrace_trace_function 略有不同第二个参数selfpc是相同的但第一个参数应该是指向frompc的指针。 通常它位于堆栈上。这允许函数暂时劫持返回地址使其指向特定于 arch 的函数return_to_handler。 下面是更新后的 mcount 伪代码:void mcount(void){…if (ftrace_trace_function ! ftrace_stub)goto do_trace;#ifdef CONFIG_FUNCTION_GRAPH_TRACER extern void (*ftrace_graph_return)(…); extern void (ftrace_graph_entry)(…); if (ftrace_graph_return ! ftrace_stub || ftrace_graph_entry ! ftrace_graph_entry_stub) ftrace_graph_caller();#endif/ restore any bare state /…下面是新的 ftrace_graph_caller 汇编函数的伪代码:#ifdef CONFIG_FUNCTION_GRAPH_TRACERvoid ftrace_graph_caller(void){/ save all state needed by the ABI */unsigned long frompc …;unsigned long selfpc return address - MCOUNT_INSN_SIZE;/ passing frame pointer up is optional – see below /prepare_ftrace_return(frompc, selfpc, frame_pointer);/ restore all state needed by the ABI /}#endif下面是新的 return_to_handler 汇编函数的伪代码。#ifdef CONFIG_FUNCTION_GRAPH_TRACERvoid return_to_handler(void){/ save all state needed by the ABI (see paragraph above) */void (original_return_point)(void) ftrace_return_to_handler();/ restore all state needed by the ABI // this is usually either a return or a jump */original_return_point();}#endif2.2 HAVE_DYNAMIC_FTRACE 选项对动态 ftrace 的支持 针对上实现后续即可由调用函数指针实现函数的记录等操作最终展示在 ftrace 中。然而实际上上述实现是对性能损耗有影响的因为每个函数都会调用mcount。 针对这个问题内核设计实现了HAVE_DYNAMIC_FTRACE动态 ftrace 机制该机制可以在没有激活 ftrace 的情况下将所有 mcount 调用替换为 nop 操作这样在没有激活 ftrace 时对系统性能的影响几乎可以忽略不计。 在了解 HAVE_DYNAMIC_FTRACE 之前还需要HAVE_FTRACE_MCOUNT_RECORD支持HAVE_FTRACE_MCOUNT_RECORD会在编译阶段收集 mcount 信息并生成mcount_loc以便于收集所有mcount信息并进行处理。 在内核中有一个 recordmcount.pl脚本当我们选中HAVE_FTRACE_MCOUNT_RECORD时则会在编译每个 C 文件生成 Object 文件时调用该脚本该脚本会为每个 object 文件创建一个名为mcount_loc的段其中保存了对 mcount 调用的所有偏移。 每个文件 obj 保存了__mcount_loc段该段的每个部分保存了指向 mcount调用者的指针列表并在最终链接 vmlinux 中在 .init中使用 start_mcount_loc和end_mcount_loc之间保存了所有 mcount 调用者列表后续启动内核时内核读取该表保存初始化相关数据并将其对应调用处转换为nops指令。 当以后启动跟踪或分析时这些位置将被替换回指向某个函数的指针。 __mcount_loc中保存的偏移是基于段开头开始的而不是每个函数的开头。 但是这个部分最终在 vmlinux 中的位置目前还不能确定。所以还不能用这种偏移量来记录这个点的最终地址。 recordmcount.pl中使用了一个诀窍是将引用节开头的调用偏移量更改为引用该节中的函数符号。在链接步骤中ld将根据我们记录的信息计算最终地址。

e.g.

#

.section .sched.text, ax

[…]

func1:

[…]

call mcount (offset: 0x10)

[…]

ret

.globl fun2

func2: (offset: 0x20)

[…]

[…]

ret

func3:

[…]

call mcount (offset: 0x30)

[…]#在上面的例子中两个重定位的偏移量都将从.schedule .text中偏移。如果我们选择全局符号func2作为引用并创建另一个文件tmp.S与新的偏移量:# .section __mcount_loc

.quad func2 - 0x10

.quad func2 0x10然后我们可以编译这个tmp.S写入tmp.O并将其链接回原始对象。在我们的算法中我们将选择在本节中遇到的第一个全局函数作为引用。 但如果本节中没有全局函数这就很难了。 在这种情况下我们必须选择一个本地的。

例如func1:

.section .sched.text, ax

func1:

[…]

call mcount (offset: 0x10)

[…]

ret

func2:

[…]

call mcount (offset: 0x20)

[…]

.section other.section

如果我们创建tmp,与上面一样当我们与原始对象链接在一起时 我们将得到func1的两个符号:一个局部符号一个全局符号。 在最终编译之后最终会得到对func1的未定义引用或者对其他文件中的另一个全局func1的错误引用。 由于局部对象可以引用局部变量我们需要找到一种方法来创建tmp。 在将原始对象文件链接在一起后不引用其本地对象。 为此我们在链接tmp.o之前将func1转换为全局符号。 然后我们链接tmp。对于func1我们将只有一个全局符号。我们可以将func1转换回局部符号这样就完成了。所以生成__mcount_loc有如下步骤

  1. 用“nm”记录所有的局部和弱符号。
  2. 使用objdump查找所有调用站点偏移量和mcount的部分。
  3. 将列表编译成它自己的对象。
  4. 我们必须处理局部函数吗?否执行步骤8。
  5. 用objcopy创建一个对象将这些局部函数转换为全局符号。
  6. 将这个新对象与列表对象链接在一起。
  7. 将局部函数转换回局部符号并将结果重命名为原始对象。
  8. 将该对象与列表对象链接。
  9. 将结果移回原始对象。至此就可以在 vmlinux 中生成mcount_loc并且动态的替换。 当我们支持 HAVE_DYNAMIC_FTRACE时默认会选中HAVE_FTRACE_MCOUNT_RECORD。 接下来当要去实现动态 mcount时arch 需要如下的实现 - asm/ftrace.h:- MCOUNT_ADDR- ftrace_call_adjust()- struct dyn_arch_ftrace{}- asm code:- mcount() (new stub)- ftrace_caller()- ftrace_call()- ftrace_stub()- C code:- ftrace_dyn_arch_init()- ftrace_make_nop()- ftrace_make_call()- ftrace_update_ftrace_func()针对动态 ftrace 则不再需要 arch 去实现 mcount 了arm64 如下所有功能在 ftrace 中实现 #else /* CONFIG_DYNAMIC_FTRACE / /** _mcount() is used to build the kernel with -pg option, but all the branch instructions to _mcount() are replaced to NOP initially at kernel start up,* and later on, NOP to branch to ftrace_caller() when enabled or branch to* NOP when disabled per-function base.*/ ENTRY(_mcount)ret ENDPROC(_mcount) EXPORT_SYMBOL(_mcount) NOKPROBE(_mcount) …当然针对上述手动生成mcount_loc的方式有些 gcc 支持直接生成__mcount_loc表而不需要调用recordmcount.pl脚本来手动生成。 所以 Makefile 有对-mrecord-mcount选项的测试一旦测试通过说明支持自动生成 __mcount_loc此时为编译添加-mrecord-mcount选项。 另外在 GCC 4.62010 引入了 -mfentry选项把每个函数的prologue后面的 call mcount改成了在prologue前的 call fentry。原因是mcount有一个弊端是stack frame size难以确定ftrace不能访问trace的参数。2011年d57c5d51a30添加了x86-64的-mfentry支持。 GCC r215629 (2014)引入-mrecord-mcount、-mnop-mcount -mrecord-mcount 用于代替 linux/scripts/record_mcount.{pl,c}。 -mnop-mcount 不可用于 PIC把fentry替换成 NOP。 截至今天-mnop-mcount只有x86和SystemZ支持。ifdef CONFIG_FUNCTION_TRACER ifdef CONFIG_FTRACE_MCOUNT_RECORD# gcc 5 supports generating the mcount tables directlyifeq (\((call cc-option-yn,-mrecord-mcount),y)CC_FLAGS_FTRACE -mrecord-mcountexport CC_USING_RECORD_MCOUNT : 1endififdef CONFIG_HAVE_NOP_MCOUNTifeq (\)(call cc-option-yn, -mnop-mcount),y)CC_FLAGS_FTRACE -mnop-mcountCC_FLAGS_USING -DCC_USING_NOP_MCOUNTendifendif endif ifdef CONFIG_HAVE_FENTRYifeq ($(call cc-option-yn, -mfentry),y)CC_FLAGS_FTRACE -mfentryCC_FLAGS_USING -DCC_USING_FENTRYendif endif3 ftrace 的初始化 通过前面可以看到当不支持动态 ftrace 时我们直接根据 ftrace_trace_function函数指针调用没有额外的初始化过程而对于动态 ftrace 将会调用 ftrace_init对__mcount_loc进行初始化 ftrace_init- ftrace_process_locs最后生成的样子如下面的示意图ftrace_pages_start|vftrace_page—————————–|index ||size || (int) | array of dyn_ftrace|records | ——————– ——————–| (struct dyn_ftrace) |—-|ip | | … | | || | |flags | | | | || | |arch | | | | ||next | ——————– ——————–| (struct ftrace_page) |—————————–||vftrace_page—————————–|index ||size || (int) | array of dyn_ftrace|records | ——————– ——————–| (struct dyn_ftrace) |—-|ip | | … | | || | |flags | | | | ||next | |arch | | | | || (struct ftrace_page) | ——————– ————————————————-||vftrace_page—————————–|index ||size || (int) | array of dyn_ftrace|records | ——————– ——————–| (struct dyn_ftrace) |—-|ip | | … | | || | |flags | | | | ||next | |arch | | | | || (struct ftrace_page) | ——————– ————————————————-后续探针就以ftrace_pages_start为起始的一张表中。为了遍历这张特殊的表访问到其中的每一个dyn_ftrace entry就引入了这么一个宏定义遍历整张表 /** This is a double for. Do not use break to break out of the loop, you must use a goto.*/ #define do_for_each_ftrace_rec(pg, rec) \for (pg ftrace_pages_start; pg; pg pg-next) { \int __i; \for (__i 0; _____i pg-index; __i) { \rec pg-records[i];接着在调用 ftrace_update_code将所有探针地址替换为 nop 指令 ftrace_init- ftrace_process_locs- ftrace_update_codestatic int ftrace_update_code(struct module *mod, struct ftrace_page new_pgs) { …for (pg new_pgs; pg; pg pg-next) {for (i 0; i pg-index; i) {/ If something went wrong, bail without enabling anything /if (unlikely(ftrace_disabled))return -1;p pg-records[i];p-flags rec_flags;// 如果编译没有替换为我们替换为 nop则我们手动调用 ftrace_code_disable 来替换 #ifndef CC_USING_NOP_MCOUNT/** Do the initial record conversion from mcount jump to the NOP instructions.*/if (!ftrace_code_disable(mod, p))break; #endifupdate_cnt;}} … }ftrace_code_disable- ftrace_make_nop// arm64 架构 int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,unsigned long addr) {unsigned long pc rec-ip;u32 old 0, new;long offset (long)pc - (long)addr; …// 找到原来的指令 insnold aarch64_insn_gen_branch_imm(pc, addr,AARCH64_INSN_BRANCH_LINK); // 生成一个 nop 指令的 insnnew aarch64_insn_gen_nop();// 用新 insn 替换原来的 insnreturn ftrace_modify_code(pc, old, new, validate); }到这里 ftrace 的基本初始化完成后续可以在 tracefs 中设置各类 tracercurrent_tracer来填充 ftrace_trace_function函数指针即可调用对应 tracer 的回调并进行相应处理。 4 function trace 流程 目前所有的 function trace 处于关闭状态当我们使用 echo function current_tracer时会配置 tracer 为 function trace此时会根据set_ftrace_filter文件配置开启需要探测的函数默认是全部我们也可以通过写 set_ftrace_filter文件来开启我们想要探测的函数这里以写set_ftrace_filter为例。 首先是 set_ftrace_filter回调定义 trace_create_file(set_ftrace_filter, 0644, parent,ops, ftrace_filter_fops);static const struct file_operations ftrace_filter_fops {.open ftrace_filter_open,.read seq_read,.write ftrace_filter_write,.llseek tracing_lseek,.release ftrace_regex_release, };当我们写时会调用ftrace_filter_write完成 trace 设置 ftrace_filter_write- ftrace_process_regex- ftrace_match_records- match_recordsstatic int match_records(struct ftrace_hash *hash, char *func, int len, char *mod) { …do_for_each_ftrace_rec(pg, rec) {if (rec-flags FTRACE_FL_DISABLED)continue;if (ftrace_match_record(rec, func_g, mod_match, exclude_mod)) {ret enter_record(hash, rec, clear_filter);if (ret 0) {found ret;goto out_unlock;}found 1;}} while_for_each_ftrace_rec(); …ftrace_match_record中会根据 function name 与 mcount_loc 中每条 rec-ip 然后按照kallsyms_lookup来匹配函数名一旦匹配上则调用enter_record 根据 rec-ip 计算 hash 并记录到全局 ftrace_hash 表中后续根据ftrace_hash 来真正替换那些需要探测点。 实际替换发生在 set_ftrace_filter文件关闭阶段 ftrace_regex_release- ftrace_hash_move_and_update_ops- ftrace_hash_move 1- ftrace_ops_update_code 21第一步将本地记录的 record 表同步到全局变量 ftrace_ops 中。 2真正替换发生在第二步 ftrace_ops_update_code ftrace_ops_update_code- ftrace_run_modify_code- ftrace_run_update_code- ftrace_arch_code_modify_prepare- arch_ftrace_update_code- ftrace_modify_all_code- ftrace_update_ftrace_func- ftrace_replace_code- ftrace_arch_code_modify_post_processftrace_update_ftrace_func首先会将原来 arch 中定义的 ftrace_call 替换为 ftrace_ops_list_func 接着 ftrace_replace_code将会开始真正替换所有 mcount ftrace_replace_code- do_for_each_ftrace_rec {ftrace_replace_code// 在这里会根据配置等判断具体怎么替换对于 function 这里替换为如下- ftrace_make_call}static int __ftrace_replace_code(struct dyn_ftrace rec, int enable) {…ftrace_addr ftrace_get_addr_new(rec);case FTRACE_UPDATE_MAKE_CALL:ftrace_bug_type FTRACE_BUG_CALL;return ftrace_make_call(rec, ftrace_addr); …ftrace_get_addr_new在这里实际返回的就是 ftrace_caller #ifndef FTRACE_ADDR #define FTRACE_ADDR ((unsigned long)ftrace_caller) #endif// arm64 动态 ftrace ENTRY(ftrace_caller)mcount_entermcount_get_pc0 x0 // functions pcmcount_get_lr x1 // functions lrGLOBAL(ftrace_call) // tracer(pc, lr);nop // This will be replaced with bl xxx// where xxx can be any kind of tracer.#ifdef CONFIG_FUNCTION_GRAPH_TRACER GLOBAL(ftrace_graph_call) // ftrace_graph_caller();nop // If enabled, this will be replaced// b ftrace_graph_caller #endifmcount_exit ENDPROC(ftrace_caller) #endif / CONFIG_DYNAMIC_FTRACE */ENTRY(ftrace_stub)ret ENDPROC(ftrace_stub)可以看到基于上述替换后进入函数首先执行ftrace_caller在 ftrace_caller中继续调用上面替换的ftrace_call而这里的 ftrace_call指向的则是ftrace_ops_list_func也就是说在 ftrace 开启下每个函数将会调用 ftrace_ops_list_func。 ftrace_ops_list_func- __ftrace_ops_list_funcstatic inline void __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,struct ftrace_ops *ignored, struct pt_regs regs) { …do_for_each_ftrace_op(op, ftrace_ops_list) {/** Check the following for each ops before calling their func: if RCU flag is set, then rcu_is_watching() must be true* if PER_CPU is set, then ftrace_function_local_disable()* must be false* Otherwise test if the ip matches the ops filter** If any of the above fails then the op-func() is not executed.*/if ((!(op-flags FTRACE_OPS_FL_RCU) || rcu_is_watching()) ftrace_ops_test(op, ip, regs)) {if (FTRACE_WARN_ON(!op-func)) {pr_warn(op%p %pS\n, op, op);goto out;}op-func(ip, parent_ip, op, regs);}} while_for_each_ftrace_op(op); …从这里可以看到会去遍历ftrace_ops_list链表并且使其与对应 ops 匹配一旦匹配上则会调用 op-func 而这里的 op 实际则是对应我们在 current_tracer中设置的 tracer。 ftrace_ops_list链表保存了当前系统所有注册的tracer。tracer是一个struct ftrace_ops结构体每一个 tracer都会去实现自己的 ftrace_ops。 struct ftrace_ops {ftrace_func_t func; // 对应 tracer 调用的 funcstruct ftrace_ops __rcu *next; // 下一个 tracerunsigned long flags;void *private;ftrace_func_t saved_func;// 当使用动态 ftrace 时使用下面的数据与 current_tracer 匹配 #ifdef CONFIG_DYNAMIC_FTRACEstruct ftrace_ops_hash local_hash;struct ftrace_ops_hash *func_hash;struct ftrace_ops_hash old_hash;unsigned long trampoline;unsigned long trampoline_size; #endif };那么这个结构体又怎么被写到全局链表中呢。这里涉及另一个结构体struct tracer每个tracer真正实现的是这个数据结构比如 irqblkfunction等。这里我们还是以function为例 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); } 到这里一个 tracer 便被添加到了系统中。当我们 echo function current_tracer 时系统根据我们注册的 tracer-name 和 buf 比较一旦比较成功有如下代码 tracing_set_trace_write- tracing_set_tracerstatic int tracing_set_tracer(struct trace_array *tr, const char *buf) { …if (tr-current_trace-reset)tr-current_trace-reset(tr);if (t-init) {ret tracer_init(t, tr);if (ret)goto out;} …int tracer_init(struct tracer *t, struct trace_array *tr) {tracing_reset_online_cpus(tr-trace_buffer);return t-init(tr); }也就是说会调用 function_trace 的 function_trace_init函数 static int function_trace_init(struct trace_array tr) {ftrace_func_t func;/** Instance trace_arrays get their ops allocated at instance creation. Unless it failed* the allocation./if (!tr-ops)return -ENOMEM;// 根据当前文件配置选择对应调用的回调这里是 function_trace_call/ Currently only the global instance can do stack tracing */if (tr-flags TRACE_ARRAY_FL_GLOBAL func_flags.val TRACE_FUNC_OPT_STACK)func function_stack_trace_call;elsefunc function_trace_call;// 将 func 绑定到 ftrace_ops 中。ftrace_init_array_ops(tr, func);tr-trace_buffer.cpu get_cpu();put_cpu();tracing_start_cmdline_record();// 在这里还会对非动态 ftrace 更新 ftrace_trace_function 为 functracing_start_function_trace(tr);return 0; }void ftrace_init_array_ops(struct trace_array tr, ftrace_func_t func) {/ If we filter on pids, update to use the pid function */if (tr-flags TRACE_ARRAY_FL_GLOBAL) {if (WARN_ON(tr-ops-func ! ftrace_stub))printk(ftrace ops had %pS for function\n,tr-ops-func);}tr-ops-func func;tr-ops-private tr; }至此 func 则与 ftrace_ops 绑定上当我们在__ftrace_ops_list_func中遍历到匹配的 ftrace_ops-func 是则会直接调用通过分析我们调用的是function_trace_call。 static void function_trace_call(unsigned long ip, unsigned long parent_ip,struct ftrace_ops *op, struct pt_regs *pt_regs) {struct trace_array *tr op-private;struct trace_array_cpu *data;unsigned long flags;int bit;int cpu;int pc;if (unlikely(!tr-function_enabled))return;// 记录 preempt_countpc preempt_count();preempt_disable_notrace();bit trace_test_and_set_recursion(TRACE_FTRACE_START, TRACE_FTRACE_MAX);if (bit 0)goto out;// 记录 cpuidcpu smp_processor_id();data per_cpu_ptr(tr-trace_buffer.data, cpu);if (!atomic_read(data-disabled)) {// 记录 irqlocal_save_flags(flags);// 将记录数据写入 ring_buffertrace_function(tr, ip, parent_ip, flags, pc);}trace_clear_recursion(bit);out:preempt_enable_notrace(); }void trace_function(struct trace_array *tr,unsigned long ip, unsigned long parent_ip, unsigned long flags,int pc) {struct trace_event_call *call event_function;struct ring_buffer *buffer tr-trace_buffer.buffer;struct ring_buffer_event *event;struct ftrace_entry *entry;// 和 tracepoint 类似首先需要申请一个写入 ring_buffer 的记录数据空间event trace_buffer_lock_reserve(buffer, TRACE_FN, sizeof(*entry),flags, pc);if (!event)return;// 记录当前探测点的 ip 以及父亲 ip以便于 trace 中的显示。entry ring_buffer_event_data(event);entry-ip ip;entry-parent_ip parent_ip;// ok 数据过滤没有问题直接将记录的 entry 提交的 ring_buffer 中。if (!call_filter_check_discard(call, entry, buffer, event)) {if (static_branch_unlikely(ftrace_exports_enabled))ftrace_exports(event);buffer_unlock_commit(buffer, event);} }直到这里 ftrace 的调用基本完成后续即可通过 trace文件读取数据。同 tracepoint 一样会有对应的格式化 function 被调用来处理记录的 function trace 数据这里不再演示。 5 总结 通过 ftrace 分析可以看到基于 function trace内核拓展了 ftrace 框架使其各自可以定义自己的 tracer 来跟踪调试内核加上 trace evnet 机制以及基于此设施的 perf 和 bpf 使其形成了一个庞大内核调试框架。而这一切通过几个系统调用和 tracefs 文件系统接口完成在。