美文网首页Linux Tracer
内核ftrace的实现

内核ftrace的实现

作者: goldhorn | 来源:发表于2017-11-03 23:31 被阅读0次

在动态ftrace原理中已经介绍了内核通过gcc -pg -fentry为函数增加5 Byte的stub,系统启动后这5 Byte被修改为NOP指令:66 66 66 66 90H。

开始trace时要将NOP指令修改为跳转指令,去执行各种trace对应的hook函数。function trace对应的hook函数就是function_trace_call。

本文将会介绍内核是如何修改代码段以控制函数去执行指定的hook函数。

运行时修改代码段

系统运行时修改代码段是一个很危险的操作,因为被修改的5 Byte有可能跨两个cache line,如果其它Core正在执行,有可能取到被修改了一半的结果,导致系统crash。

ftrace修改代码段是在ftrace_replace_code中完成的,这个函数里有三个大循环

  • add_breakpoints: 首先找到需要trace的函数,将第一个字节修改为0xCC,即int 3(也叫break指令)
  • add_update: 修改为callq trampoline指令,但是第一个字节保留为0xCC
  • finish_update: 将0xCC修改为0xE8,即为call指令
# echo expand_files > set_ftrace_filter
# echo function > current_tracer 

以上面的操作举例,配置ftrace跟踪expand_files函数,该函数前5 Byte变化如下面所示:

0xffffffff8114aae0 <expand_files>: 
66 66 66 66 90H        <-- NOP
    |
    |
    V
CC 66 66 66 90H        <-- int 3
    |
    |
    V
CC 1b 55 eb 1eH        <-- 跳转的偏移已经修改好了,但opcode还是int 3
    |
    |
    V
e8 1b 55 eb 1eH        <-- callq  0xffffffffa0000000

内核在修改代码段时先将第一个Byte修改为0xCC,如果有其它Core执行到这里会触发异常,但是在int 3异常处理程序中直接返回并再次触发异常,直至int 3被修改为call指令后才跳出循环

dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)
{
#ifdef CONFIG_DYNAMIC_FTRACE
    /*
     * ftrace must be first, everything else may cause a recursive crash.
     * See note by declaration of modifying_ftrace_code in ftrace.c
     */
    if (unlikely(atomic_read(&modifying_ftrace_code)) &&
        ftrace_int3_handler(regs))
        return;
#endif
    
    ...

跳转目标

前面说到trace的原理是修改函数开始的5 Byte,让其先去执行指定的hook函数。不同的tracer有不同的hook函数,function tracer的hook函数是function_trace_call,这个函数的功能比较简单,只是向ring buffer中记录了ip和parent_ip

内核提供了<font color=cornflowerblue>.ftrace_caller</font>和<font color=cornflowerblue>.ftrace_regs_caller</font>两段汇编代码作为wrapper,用来完成保存/恢复寄存器等通用的工作,其中的<font color=cornflowerblue>call ftrace_stub</font >会被修改为各种tracer对应的hook function

ENTRY(ftrace_caller)
    /* save_mcount_regs fills in first two parameters */
    save_mcount_regs

GLOBAL(ftrace_caller_op_ptr)
    /* Load the ftrace_ops into the 3rd parameter */
    movq function_trace_op(%rip), %rdx

    /* regs go into 4th parameter (but make it NULL) */
    movq $0, %rcx

GLOBAL(ftrace_call)
    call ftrace_stub

    restore_mcount_regs

GLOBAL(ftrace_caller_end)

GLOBAL(ftrace_return)

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
GLOBAL(ftrace_graph_call)
    jmp ftrace_stub
#endif

GLOBAL(ftrace_stub)
    retq
END(ftrace_caller)

但是内核也没有直接调用<font color=cornflowerblue>.ftrace_caller</font>和<font color=cornflowerblue>.ftrace_regs_caller</font>,而是在内存中构造了一个trampoline,将<font color=cornflowerblue>.ftrace_caller</font>拷贝到这段trampoline中,并修改其中的相对偏移。

多个tracer同时工作

未完待续

相关文章

  • 内核ftrace的实现

    在动态ftrace原理中已经介绍了内核通过gcc -pg -fentry为函数增加5 Byte的stub,系统启动...

  • 动态追踪技术(二):trace your kernel Func

    Ftrace是一个设计用来帮助开发者和设计者监视内核的追踪器,可用于调试或分析延迟以及性能问题。ftrace令人印...

  • Linux内核ftrace原理

    gcc的-pg选项 ftrace 支持动态trace,即可以跟踪内核和模块中任意的全局函数。它利用了gcc的-pg...

  • 【linux内核源码】 io操作之read

    准备 本文所涉及的源码全部基于linux内核5.15。 ftrace 由于系统调用的路径比较复杂,当我们首次阅读内...

  • 【linux内核调试】ftrace/kprobes/System

    一、调试简介 本文主要实践一下linux内核调试方式,并进行比较。内核调试方式在这篇blog中讲解的非常详细,本文...

  • 【Linux内核调试】使用Ftrace来Hook linux内核

    目标:hook几个Linux内核函数调用,如打开文件和启动进程,并利用它来启用系统活动监控并抢先阻止可疑进程。 一...

  • systrace实现原理

    systrace是通过atrace和ftrace一起实现。 抓取systrace的adb命令如下:adb shel...

  • ftrace

    ftrace.txt - kernel.org[https://www.kernel.org/doc/Docume...

  • 使用 ftrace 来跟踪系统问题 - ftrace 介绍

    虽然之前一直听说过 ftrace,但从来没将它用在实战中,在一次客户排查问题中,遇到了比较奇怪的现象,一位精通内核...

  • Java线程

    线程的实现 1、使用内核线程实现 内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核...

网友评论

    本文标题:内核ftrace的实现

    本文链接:https://www.haomeiwen.com/subject/ssgxmxtx.html