Linux ftrace 1.3、tracer (function、function_graph、irq_off)

如trace event一章的描述,任何一种trace都离不开以下流程:

  • 函数插桩。使用各种插桩方式把自己的trace函数插入到需要跟踪的probe point上;
  • input trace数据。在trace的probe函数被命中时,会存储数据到ringbuffer当中;这里还包括filter和trigger功能;
  • ouput trace数据。用户或者程序需要读出trace数据,根据需要输出ram数据或者是方面用户阅读的数据;对数据的解析,遵循谁存储谁提供解析规则的原则;

以function tracer为首的tracer也遵循同样的流程。

1、fucntion tracer

function tracer最大的难点在于:dynamic ftrace时(如不做说明本文默认dynamic)对大量插桩点的管理。

root:/d/tracing # cat available_filter_functions | wc -l
49099
root:/d/tracing #
root:/d/tracing # cat available_events | wc -l
987

可以看到上述一个普通的系统,function tracer的插桩点为49099个,trace event的插桩点为987个。function tracer的插桩点几乎大于trace event插桩点两个数量级,而且trace event的插桩点是每一个插桩点都是独立控制的,而function tracer的插桩点默认是一起控制的(也可以通过set_ftrace_filter、set_ftrace_notrace来分开控制)。

这么庞大的数量的插桩点。如果独立控制会消耗大量的内存,如果集中控制又怎么能实现部分的filter和command控制?一旦桩函数的实现不简洁,开销会迅速放大。对这部分感兴趣可以直接跳转到1.2.2、插桩点的动态管理

1.1、插桩原理

1.1.1、_mcount()

众所周知function trace是利用_mcount()函数进行插桩的。在gcc使用了“-pg”选项以后,会在每个函数的入口插入以下语句:

function:
    ...
    mov x0, x30
    bl _mcount
    [function's body ...]

默认的_mcount函数是空操作:

ENTRY(_mcount)
    ret
ENDPROC(_mcount)

每个函数入口插入对_mcount()函数的调用,就是gcc提供的插桩机制。我们可以重新定义_mcount()函数中的内容,调用想要执行的内容。

1.1.2、static ftrace插桩

static ftrace就是使用重定义_mcount()函数的方法来实现插桩的:

这里写图片描述

arch/arm64/kernel/entry-ftrace.S:

#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
 */
ENTRY(_mcount)
    mcount_enter

    adrp    x0, ftrace_trace_function
    ldr x2, [x0, #:lo12: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);

#ifndef CONFIG_FUNCTION_GRAPH_TRACER
skip_ftrace_call:           //   return;
    mcount_exit         // }
#else
    mcount_exit         //   return;
                    // }
skip_ftrace_call:
    adrp    x1, ftrace_graph_return
    ldr x2, [x1, #:lo12:ftrace_graph_return]
    cmp x0, x2          //   if ((ftrace_graph_return
    b.ne    ftrace_graph_caller //        != ftrace_stub)

    adrp    x1, ftrace_graph_entry  //     || (ftrace_graph_entry
    adrp    x0, ftrace_graph_entry_stub //     != ftrace_graph_entry_stub))
    ldr x2, [x1, #:lo12:ftrace_graph_entry]
    add x0, x0, #:lo12:ftrace_graph_entry_stub
    cmp x0, x2
    b.ne    ftrace_graph_caller //     ftrace_graph_caller();

    mcount_exit
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
ENDPROC(_mcount)

#else /* CONFIG_DYNAMIC_FTRACE */

对应的伪代码如下:

void _mcount(void)
{
        /* save any bare state needed in order to do initial checking */

        /* (1) 如果函数指针ftrace_trace_function不等于默认的桩函数ftrace_stub, 
            调用function tracer的回调函数:ftrace_trace_function()
         */
        extern void (*ftrace_trace_function)(unsigned long, unsigned long);
        if (ftrace_trace_function != ftrace_stub)
                goto do_trace;

        /* (2) 如果 (ftrace_trace_function = = ftrace_stub) && 
            (ftrace_graph_return != ftrace_stub ||
            ftrace_graph_entry != ftrace_graph_entry_stub)
            调用function_graph tracer的回调函数:ftrace_graph_caller()
         */
+#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 */

        return;

do_trace:

        /* save all state needed by the ABI (see paragraph above) */

        unsigned long frompc = ...;
        unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;

        /* (1.1) 调用function tracer的回调函数:ftrace_trace_function() */
        ftrace_trace_function(frompc, selfpc);

        /* restore all state needed by the ABI */
}

问:static ftrace下,怎么保准notrace的函数不被跟踪?

1.1.3、dynamic ftrace插桩

static ftrace一旦使能,对kernel中所有的函数(除开notrace、online、其他特殊函数)进行插桩,这带来的性能开销是惊人的,有可能导致人们弃用ftrace功能。

为了解决这个问题,内核开发者推出了dynamic ftrace。因为实际上调试者一般不需要对所有函数进行追踪,只会对感兴趣的一部分函数进行追踪。dynamic ftrace把不需要追踪的函数入口处指令“bl _mcount”替换成“nop”,这样基本对性能无影响;对需要追踪的函替换入口处“bl _mcount”为需要调用的函数。

1、ftrace在初始化时,根据编译时“scripts/recordmcount.pl”脚本记录的所有函数入口处插桩位置的“bl _mcount”,将其替换成“nop”指令:

这里写图片描述

2、在tracer enable的时候,把需要跟踪的函数的插桩位置“nop”替换成“bl ftrace_caller”。

为什么不使用“bl _mcount”?主要原因是开发者不喜欢在_mcount()中使用宏来区分两种情况,索性重新创建一个新函数ftrace_caller()。

这里写图片描述

arch/arm64/kernel/entry-ftrace.S:

#ifndef CONFIG_DYNAMIC_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)

/*
 * void ftrace_caller(unsigned long return_address)
 * @return_address: return address to instrumented function
 *
 * This function is a counterpart of _mcount() in 'static' ftrace, and
 * makes calls to:
 *     - tracer function to probe instrumented function's entry,
 *     - ftrace_graph_caller to set up an exit hook
 */
ENTRY(ftrace_caller)
    mcount_enter

    mcount_get_pc0  x0      //     function's pc
    mcount_get_lr   x1      //     function's lr

    .global ftrace_call
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_call:          // ftrace_graph_caller();
    nop             // If enabled, this will be replaced
                    // "b ftrace_graph_caller"
#endif

    mcount_exit
ENDPROC(ftrace_caller)
#endif /* CONFIG_DYNAMIC_FTRACE */

对应的伪代码为:

void ftrace_caller(void)
{
        /* save all state needed by the ABI (see paragraph above) */

        unsigned long frompc = ...;
        unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;

        /* This will be replaced with "bl xxx" where xxx can be any kind of tracer. */
        /* (1) function tracer的桩位置
            放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"bl xxx"
         */
ftrace_call:
        nop     

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
        /* If enabled, this will be replaced "b ftrace_graph_caller" */
        /* (2) function_graph tracer的桩位置
            放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"b ftrace_graph_caller"
         */
ftrace_graph_call:
        nop    
#endif

        /* restore all state needed by the ABI */

        return;
}

1.2、插桩点管理

1.2.1、插桩点的初始化

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

include/asm-generic/vmlinux.lds.h:

#ifdef CONFIG_FTRACE_MCOUNT_RECORD
#define MCOUNT_REC()    . = ALIGN(8);               \
            VMLINUX_SYMBOL(__start_mcount_loc) = .; \
            *(__mcount_loc)             \
            VMLINUX_SYMBOL(__stop_mcount_loc) = .;
#else
#define MCOUNT_REC()
#endif

并没有详细去研究其原理,感兴趣可以具体研究一下“scripts/recordmcount.pl、scripts/recordmcount.c”。

2、在初始化时,遍历section __mcount_loc的调用点地址,默认给所有“bl _mcount”替换成“nop”。

kernel/trace/ftrace.c:

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);

    ret = register_module_notifier(&ftrace_module_exit_nb);
    if (ret)
        pr_warning("Failed to register trace ftrace module exit notifier\n");

    set_ftrace_early_filters();

    return;
 failed:
    ftrace_disabled = 1;
}

|→

static int ftrace_process_locs(struct module *mod,
                   unsigned long *start,
                   unsigned long *end)
{
    struct ftrace_page *start_pg;
    struct ftrace_page *pg;
    struct dyn_ftrace *rec;
    unsigned long count;
    unsigned long *p;
    unsigned long addr;
    unsigned long flags = 0; /* Shut up gcc */
    int ret = -ENOMEM;

    count = end - start;

    if (!count)
        return 0;

    /* (1) 对地址进行排序 */
    sort(start, count, sizeof(*start),
         ftrace_cmp_ips, NULL);

    /* (2) 对每个地址分配新的dyn_ftrace结构来存储 
        在section __mcount_loc中,只是简单的存储了unsigned long类型的调用地址
        dyn_ftrace结构除了使用->ip来存储地址,还使用->flags来存储当前的状态和被引用计数
     */
    start_pg = ftrace_allocate_pages(count);
    if (!start_pg)
        return -ENOMEM;

    mutex_lock(&ftrace_lock);

    /*
     * Core and each module needs their own pages, as
     * modules will free them when they are removed.
     * Force a new page to be allocated for modules.
     */
    if (!mod) {
        WARN_ON(ftrace_pages || ftrace_pages_start);
        /* First initialization */
        ftrace_pages = ftrace_pages_start = start_pg;
    } else {
        if (!ftrace_pages)
            goto out;

        if (WARN_ON(ftrace_pages->next)) {
            /* Hmm, we have free pages? */
            while (ftrace_pages->next)
                ftrace_pages = ftrace_pages->next;
        }

        ftrace_pages->next = start_pg;
    }

    /* (3) 更新dyn_ftrace新结构中的->ip字段 */
    p = start;
    pg = start_pg;
    while (p < end) {
        addr = ftrace_call_adjust(*p++);
        /*
         * Some architecture linkers will pad between
         * the different mcount_loc sections of different
         * object files to satisfy alignments.
         * Skip any NULL pointers.
         */
        if (!addr)
            continue;

        if (pg->index == pg->size) {
            /* We should have allocated enough */
            if (WARN_ON(!pg->next))
                break;
            pg = pg->next;
        }

        rec = &pg->records[pg->index++];
        rec->ip = addr;
    }

    /* We should have used all pages */
    WARN_ON(pg->next);

    /* Assign the last page to ftrace_pages */
    ftrace_pages = pg;

    /*
     * We only need to disable interrupts on start up
     * because we are modifying code that an interrupt
     * may execute, and the modification is not atomic.
     * But for modules, nothing runs the code we modify
     * until we are finished with it, and there's no
     * reason to cause large interrupt latencies while we do it.
     */
    if (!mod)
        local_irq_save(flags);
    /* (4) 更新dyn_ftrace新结构中的->flags字段 
        默认给所有调用点替换成“nop”指令
     */
    ftrace_update_code(mod, start_pg);
    if (!mod)
        local_irq_restore(flags);
    ret = 0;
 out:
    mutex_unlock(&ftrace_lock);

    return ret;
}

||→

static int ftrace_update_code(struct module *mod, struct ftrace_page *new_pgs)
{
    struct ftrace_page *pg;
    struct dyn_ftrace *p;
    cycle_t start, stop;
    unsigned long update_cnt = 0;
    unsigned long ref = 0;
    bool test = false;
    int i;

    /*
     * When adding a module, we need to check if tracers are
     * currently enabled and if they are set to trace all functions.
     * If they are, we need to enable the module functions as well
     * as update the reference counts for those function records.
     */
    if (mod) {
        struct ftrace_ops *ops;

        for (ops = ftrace_ops_list;
             ops != &ftrace_list_end; ops = ops->next) {
            if (ops->flags & FTRACE_OPS_FL_ENABLED) {
                if (ops_traces_mod(ops))
                    ref++;
                else
                    test = true;
            }
        }
    }

    start = ftrace_now(raw_smp_processor_id());

    /* (4.1) 逐个遍历dyn_ftrace结构 */
    for (pg = new_pgs; pg; pg = pg->next) {

        for (i = 0; i < pg->index; i++) {
            int cnt = ref;

            /* If something went wrong, bail without enabling anything */
            if (unlikely(ftrace_disabled))
                return -1;

            p = &pg->records[i];
            /* (4.2) 根据有多少个filter操作对调用点地址的引用, 
                给dyn_ftrace->flags字段中的引用count部分赋值
             */
            if (test)
                cnt += referenced_filters(p);
            p->flags = cnt;

            /*
             * Do the initial record conversion from mcount jump
             * to the NOP instructions.
             */
            /* (4.3) 默认把所有的调用点修改成“nop” */
            if (!ftrace_code_disable(mod, p))
                break;

            update_cnt++;

            /*
             * If the tracing is enabled, go ahead and enable the record.
             *
             * The reason not to enable the record immediatelly is the
             * inherent check of ftrace_make_nop/ftrace_make_call for
             * correct previous instructions.  Making first the NOP
             * conversion puts the module to the correct state, thus
             * passing the ftrace_make_call check.
             */
            if (ftrace_start_up && cnt) {
                /* (4.4) 如果tracing已经使能,且引用计数不为0 
                    使能这个调用点
                 */
                int failed = __ftrace_replace_code(p, 1);
                if (failed)
                    ftrace_bug(failed, p);
            }
        }
    }

    stop = ftrace_now(raw_smp_processor_id());
    ftrace_update_time = stop - start;
    ftrace_update_tot_cnt += update_cnt;

    return 0;
}

1.2.2、插桩点的动态管理

为了应对本文最开始处提出的难题,内核使用了插桩点控制链表+命令hash表的形式。

这里写图片描述

function tracer插桩点动态管理的原理:

  • 1、每个“bl _mcount”的插桩点ip对应一个每个插桩点对应一个dyn_ftrace结构。dyn_ftrace->flags的低26bit用来表示引用计数,如果有ftrcae_ops会操作到该ip,引用计数会加1。如果ref_cnt大于0,插桩点就需要使能了,把其替换为“bl ftrace_caller”;
  • 2、ftrcae_ops采用hash表来表达对ip的引用,一个ftrcae_ops有两张hash表filter_hash和notrace_hash,综合的结果就是对ip的引用。function tracer模式时,有两个可能的ftrcae_ops:global_ops、trace_probe_ops。两个中任意一个引用到ip,都会造成ip的1级插桩点被替换为“bl ftrace_caller”;
  • 3、通过设置“set_ftrace_filter/set_ftrace_notrace”会造成对global_ops的filter_hash/notrace_hash的配置,它的作用是配置function tracer的filter;
  • 4、通过设置“set_ftrace_filter”会造成对trace_probe_ops的filter_hash的配置,它的作用是配置filter command,并且会把command和对应的ip加入到另外一张另外一张hash表ftrace_func_hash表中;
  • 5、ftrace_caller()有两个2级插桩点:ftrace_call、ftrace_graph_call。在function tracer模式时,只有ftrace_call被设置成“bl ftrace_ops_list_func”;
  • 6、ftrace_ops_list_func()的工作就是遍历ftrace_ops_list链表,逐个执行其中的ftrace_ops->func()。
  • 7、global_ops的->func()函数为function_trace_call(),记录trace数据:ip + parent_ip;
  • 8、trace_probe_ops的->func()函数为function_trace_probe_call(),逐个执行ftrace_func_hash表中,ip为本ip的filter command。

有以下几种场景,涉及到插桩点的动态管理:

  • tracer的使能。当使用“echo xxx_tracer > current_tracer”时,会关闭旧的current tracer并使能新的tracer。典型的包括function tracer合入function_graph tracer;
  • filter的配置。使用“echo function_name > set_ftrace_filter/set_ftrace_notrace”,可以配置部分function被trace,而不是所有function被trace;
  • filter command的配置。使用“echo ‘!__schedule_bug:traceoff’ > set_ftrace_filter”,类似命令可以配置条件触发的command,当条件满足后command会被执行;

下面我们看看具体场景下的代码实现细节:

1.2.3、tracer注册

1、tracer注册。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
};

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

register_tracer()的实现,kernel/trace/trace.c:

int __init register_tracer(struct tracer *type)
{
    struct tracer *t;
    int ret = 0;

    if (!type->name) {
        pr_info("Tracer must have a name\n");
        return -1;
    }

    if (strlen(type->name) >= MAX_TRACER_SIZE) {
        pr_info("Tracer has a name longer than %d\n", MAX_TRACER_SIZE);
        return -1;
    }

    mutex_lock(&trace_types_lock);

    tracing_selftest_running = true;

    /* (1) 如果tracer已经注册,直接返回 */
    for (t = trace_types; t; t = t->next) {
        if (strcmp(type->name, t->name) == 0) {
            /* already found */
            pr_info("Tracer %s already registered\n",
                type->name);
            ret = -1;
            goto out;
        }
    }

    /* (2) tracer对应的相关trace_option */
    if (!type->set_flag)
        type->set_flag = &dummy_set_flag;
    if (!type->flags)
        type->flags = &dummy_tracer_flags;
    else
        if (!type->flags->opts)
            type->flags->opts = dummy_tracer_opt;

    ret = run_tracer_selftest(type);
    if (ret < 0)
        goto out;

    /* (3) 将新的tracer加入到trace_types链表中 */
    type->next = trace_types;
    trace_types = type;
    add_tracer_options(&global_trace, type);

 out:
    tracing_selftest_running = false;
    mutex_unlock(&trace_types_lock);

    if (ret || !default_bootup_tracer)
        goto out_unlock;

    if (strncmp(default_bootup_tracer, type->name, MAX_TRACER_SIZE))
        goto out_unlock;

    printk(KERN_INFO "Starting tracer '%s'\n", type->name);
    /* Do we want this tracer to start on bootup? */
    /* (4) 设置boot默认的tracer */
    tracing_set_tracer(&global_trace, type->name);
    default_bootup_tracer = NULL;

    apply_trace_boot_options();

    /* disable other selftests, since this will break it. */
    tracing_selftest_disabled = true;
#ifdef CONFIG_FTRACE_STARTUP_TEST
    printk(KERN_INFO "Disabling FTRACE selftests due to running tracer '%s'\n",
           type->name);
#endif

 out_unlock:
    return ret;
}

1.2.4、tracer使能

2、tracer使能。我们可以使用“echo function > current_tracer”命令来使能或者切换tracer。

来具体看看其代码实现:

trace_create_file("current_tracer", 0644, d_tracer,
        tr, &set_tracer_fops);

↓

static const struct file_operations set_tracer_fops = {
    .open       = tracing_open_generic,
    .read       = tracing_set_trace_read,
    .write      = tracing_set_trace_write,
    .llseek     = generic_file_llseek,
};

↓

static ssize_t
tracing_set_trace_write(struct file *filp, const char __user *ubuf,
            size_t cnt, loff_t *ppos)
{
    /* (1) 当前文件所在trace buffer,默认是global_trace 
        初始化时:
        init_tracer_tracefs(&global_trace, d_tracer); -> 
        trace_create_file("current_tracer", 0644, d_tracer, tr, &set_tracer_fops);
     */
    struct trace_array *tr = filp->private_data;
    char buf[MAX_TRACER_SIZE+1];
    int i;
    size_t ret;
    int err;

    ret = cnt;

    if (cnt > MAX_TRACER_SIZE)
        cnt = MAX_TRACER_SIZE;

    if (copy_from_user(&buf, ubuf, cnt))
        return -EFAULT;

    buf[cnt] = 0;

    /* strip ending whitespace. */
    for (i = cnt - 1; i > 0 && isspace(buf[i]); i--)
        buf[i] = 0;

    /* (2) 使能新的tracer */
    err = tracing_set_tracer(tr, buf);
    if (err)
        return err;

    *ppos += ret;

    return ret;
}

|→

static int tracing_set_tracer(struct trace_array *tr, const char *buf)
{
    struct tracer *t;
#ifdef CONFIG_TRACER_MAX_TRACE
    bool had_max_tr;
#endif
    int ret = 0;

    mutex_lock(&trace_types_lock);

    if (!ring_buffer_expanded) {
        ret = __tracing_resize_ring_buffer(tr, trace_buf_size,
                        RING_BUFFER_ALL_CPUS);
        if (ret < 0)
            goto out;
        ret = 0;
    }

    /* (2.1) 根据名字,在trace_types链表中找到对应的tracer */
    for (t = trace_types; t; t = t->next) {
        if (strcmp(t->name, buf) == 0)
            break;
    }
    if (!t) {
        ret = -EINVAL;
        goto out;
    }
    if (t == tr->current_trace)
        goto out;

    /* Some tracers are only allowed for the top level buffer */
    if (!trace_ok_for_array(t, tr)) {
        ret = -EINVAL;
        goto out;
    }

    /* If trace pipe files are being read, we can't change the tracer */
    if (tr->current_trace->ref) {
        ret = -EBUSY;
        goto out;
    }

    trace_branch_disable();

    /* (2.2) 去使能旧的当前tracer */
    tr->current_trace->enabled--;

    if (tr->current_trace->reset)
        tr->current_trace->reset(tr);

    /* Current trace needs to be nop_trace before synchronize_sched */
    tr->current_trace = &nop_trace;

#ifdef CONFIG_TRACER_MAX_TRACE
    had_max_tr = tr->allocated_snapshot;

    if (had_max_tr && !t->use_max_tr) {
        /*
         * We need to make sure that the update_max_tr sees that
         * current_trace changed to nop_trace to keep it from
         * swapping the buffers after we resize it.
         * The update_max_tr is called from interrupts disabled
         * so a synchronized_sched() is sufficient.
         */
        synchronize_sched();
        free_snapshot(tr);
    }
#endif

#ifdef CONFIG_TRACER_MAX_TRACE
    if (t->use_max_tr && !had_max_tr) {
        ret = alloc_snapshot(tr);
        if (ret < 0)
            goto out;
    }
#endif

    /* (2.3) 调用新tracer的init函数 */
    if (t->init) {
        ret = tracer_init(t, tr);
        if (ret)
            goto out;
    }

    /* (2.4) 把新的tracer设置为当前tracer */
    tr->current_trace = t;
    tr->current_trace->enabled++;
    trace_branch_enable(tr);
 out:
    mutex_unlock(&trace_types_lock);

    return ret;
}

||→

int tracer_init(struct tracer *t, struct trace_array *tr)
{
    tracing_reset_online_cpus(&tr->trace_buffer);
    return t->init(tr);
}

1.2.5、function tracer使能

3、function tracer的使能。本质上是global_ops的注册后使能。

在初始化的时候,把global_trace的->ops初始化成了global_ops。

start_kernel() -> trace_init() -> tracer_alloc_buffers:

tracer_alloc_buffers
{

    ftrace_init_global_array_ops(&global_trace);

}

↓

__init void ftrace_init_global_array_ops(struct trace_array *tr)
{
    tr->ops = &global_ops;
    tr->ops->private = tr;
}

static struct ftrace_ops global_ops = {
    .func               = ftrace_stub,
    .local_hash.notrace_hash    = EMPTY_HASH,
    .local_hash.filter_hash     = EMPTY_HASH,
    INIT_OPS_HASH(global_ops)
    .flags              = FTRACE_OPS_FL_RECURSION_SAFE |
                      FTRACE_OPS_FL_INITIALIZED |
                      FTRACE_OPS_FL_PID,
};

继续分析t->init(),以function tracer为例,调用到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.
     */
    /* (2.4.1) 这里的tr默认就是global_trace 
        这里的tr->ops默认是global_ops
     */
    if (!tr->ops)
        return -ENOMEM;

    /* (2.4.2) func为保存trace信息到ringbuffer的函数 */
    /* 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;
    else
        func = function_trace_call;

    /* (2.4.3) tr->ops->func = func */
    ftrace_init_array_ops(tr, func);

    tr->trace_buffer.cpu = get_cpu();
    put_cpu();

    tracing_start_cmdline_record();

    /* (2.4.4) 启动function tracer 
        将tr->ops也加入到ftrace_ops_list当中
     */
    tracing_start_function_trace(tr);
    return 0;
}

↓

static void tracing_start_function_trace(struct trace_array *tr)
{
    tr->function_enabled = 0;
    register_ftrace_function(tr->ops);
    tr->function_enabled = 1;
}

|→

int register_ftrace_function(struct ftrace_ops *ops)
{
    int ret = -1;

    /* (2.4.4.1) 初始化hash表 
        ops->func_hash = &ops->local_hash;
     */
    ftrace_ops_init(ops);

    mutex_lock(&ftrace_lock);

    /* (2.4.4.2) 将global_ops加入ftrace_ops_list 
        并且根据情况,修改各个桩位置的指令
     */
    ret = ftrace_startup(ops, 0);

    mutex_unlock(&ftrace_lock);

    return ret;
}

||→

static int ftrace_startup(struct ftrace_ops *ops, int command)
{
    int ret;

    if (unlikely(ftrace_disabled))
        return -ENODEV;

    /* (2.4.4.2.1) 1、把global_ops加入ftrace_ops_list 
        2、根据ftrace_ops_list链表中成员的情况给ftrace_trace_function指针赋值:
            ftrace_ops_list链表为空,= ftrace_stub
            ftrace_ops_list链表有1个成员,= ftrace_ops_get_list_func(ftrace_ops_list)
            ftrace_ops_list链表有多个成员,= ftrace_ops_list_func
        3、更新ftrace_graph_entry
    */
    ret = __register_ftrace_function(ops);
    if (ret)
        return ret;

    ftrace_start_up++;
    command |= FTRACE_UPDATE_CALLS;

    /*
     * Note that ftrace probes uses this to start up
     * and modify functions it will probe. But we still
     * set the ADDING flag for modification, as probes
     * do not have trampolines. If they add them in the
     * future, then the probes will need to distinguish
     * between adding and updating probes.
     */
    ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING;

    /* (2.4.4.2.2) 遍历全部_mcount插桩点ftrace_rec 
        根据ip在新、旧hash表中的变化,设置对应rec->flags中的FTRACE_FL_IPMODIFY
     */
    ret = ftrace_hash_ipmodify_enable(ops);
    if (ret < 0) {
        /* Rollback registration process */
        __unregister_ftrace_function(ops);
        ftrace_start_up--;
        ops->flags &= ~FTRACE_OPS_FL_ENABLED;
        return ret;
    }

    /* (2.4.4.2.3) 遍历全部_mcount插桩点ftrace_rec 
        根据filter_hash、notrace_hash是否match ip,给对应rec->flags中ref_cnt进行加1/减1操作
     */
    ftrace_hash_rec_enable(ops, 1);

    /* (2.4.4.2.4) 更新插桩点: 
        FTRACE_UPDATE_CALLS被设置,更新_mcount插桩点:ref_cnt大于0的插桩点,更新成ftrace_caller()
        FTRACE_UPDATE_TRACE_FUNC被设置,更新ftrace_call插桩点:更新成ftrace_trace_function指向的函数
        FTRACE_START_FUNC_RET被设置,更新ftrace_graph_call插桩点:更新成ftrace_graph_caller()
     */
    ftrace_startup_enable(command);

    ops->flags &= ~FTRACE_OPS_FL_ADDING;

    return 0;
}

↓

ftrace_startup_enable() -> ftrace_run_update_code() -> arch_ftrace_update_code() -> ftrace_modify_all_code() -> 

void ftrace_modify_all_code(int command)
{
    int update = command & FTRACE_UPDATE_TRACE_FUNC;
    int err = 0;

    /*
     * If the ftrace_caller calls a ftrace_ops func directly,
     * we need to make sure that it only traces functions it
     * expects to trace. When doing the switch of functions,
     * we need to update to the ftrace_ops_list_func first
     * before the transition between old and new calls are set,
     * as the ftrace_ops_list_func will check the ops hashes
     * to make sure the ops are having the right functions
     * traced.
     */
    /* (2.4.4.2.4.1) 如果FTRACE_UPDATE_TRACE_FUNC被设置,对于ftrace_call插桩点,
        直接调用ftrace_ops_list链表中某个ftrace_ops的操作需要谨慎 
        保险起见,默认还是使用ftrace_ops_list_func(),它会轮询ftrace_ops_list链表中所有ftrace_ops
     */
    if (update) {
        err = ftrace_update_ftrace_func(ftrace_ops_list_func);
        if (FTRACE_WARN_ON(err))
            return;
    }

    /* (2.4.4.2.4.2) 如果FTRACE_UPDATE_CALLS被设置,对于_mcount插桩点,
        遍历全部ftrace_rec,ref_cnt大于0的插桩点,更新成ftrace_caller()
     */
    if (command & FTRACE_UPDATE_CALLS)
        ftrace_replace_code(1);
    else if (command & FTRACE_DISABLE_CALLS)
        ftrace_replace_code(0);

    /* (2.4.4.2.4.3) 如果FTRACE_UPDATE_TRACE_FUNC被设置,对于ftrace_call插桩点,
        如果ftrace_trace_function确实不等于ftrace_ops_list_func()
        更新成ftrace_trace_function指向的函数
     */
    if (update && ftrace_trace_function != ftrace_ops_list_func) {
        function_trace_op = set_function_trace_op;
        smp_wmb();
        /* If irqs are disabled, we are in stop machine */
        if (!irqs_disabled())
            smp_call_function(ftrace_sync_ipi, NULL, 1);
        err = ftrace_update_ftrace_func(ftrace_trace_function);
        if (FTRACE_WARN_ON(err))
            return;
    }

    /* (2.4.4.2.4.4) 如果FTRACE_START_FUNC_RET被设置,对于ftrace_graph_call插桩点,
        更新成ftrace_graph_caller()
     */
    if (command & FTRACE_START_FUNC_RET)
        err = ftrace_enable_ftrace_graph_caller();
    else if (command & FTRACE_STOP_FUNC_RET)
        err = ftrace_disable_ftrace_graph_caller();
    FTRACE_WARN_ON(err);
}

↓

void __weak ftrace_replace_code(int enable)
{
    struct dyn_ftrace *rec;
    struct ftrace_page *pg;
    int failed;

    if (unlikely(ftrace_disabled))
        return;

    /* 遍历ftrace_rec */
    do_for_each_ftrace_rec(pg, rec) {
        failed = __ftrace_replace_code(rec, enable);
        if (failed) {
            ftrace_bug(failed, rec);
            /* Stop processing */
            return;
        }
    } while_for_each_ftrace_rec();
}

↓

static int
__ftrace_replace_code(struct dyn_ftrace *rec, int enable)
{
    unsigned long ftrace_old_addr;
    unsigned long ftrace_addr;
    int ret;

    /* (2.4.4.2.4.2.1) _mcount插桩点使能后的地址为: 
        ops->trampoline or ftrace_caller
     */
    ftrace_addr = ftrace_get_addr_new(rec);

    /* This needs to be done before we call ftrace_update_record */
    ftrace_old_addr = ftrace_get_addr_curr(rec);


    /* (2.4.4.2.4.2.2) 根据rec->flags中的flag和refcnt,以及enable 
        得出操作是:FTRACE_UPDATE_IGNORE/FTRACE_UPDATE_MAKE_CALL/FTRACE_UPDATE_MAKE_NOP
     */
    ret = ftrace_update_record(rec, enable);

    /* (2.4.4.2.4.2.3) 修改_mcount插桩点处的指令为新的桩函数跳转指令 */
    switch (ret) {
    case FTRACE_UPDATE_IGNORE:
        return 0;

    case FTRACE_UPDATE_MAKE_CALL:
        return ftrace_make_call(rec, ftrace_addr);

    case FTRACE_UPDATE_MAKE_NOP:
        return ftrace_make_nop(NULL, rec, ftrace_old_addr);

    case FTRACE_UPDATE_MODIFY_CALL:
        return ftrace_modify_call(rec, ftrace_old_addr, ftrace_addr);
    }

    return -1; /* unknow ftrace bug */
}

1.2.6、配置function tracer的filter

4、通过“set_ftrace_filter”、“set_ftrace_notrace”设置function trcer的filter。本质上是操作global_ops的filter_hash、notrace_hash。

/* global_ops注册成inode->i_private */
ftrace_create_filter_files(&global_ops, d_tracer);

↓

void ftrace_create_filter_files(struct ftrace_ops *ops,
                struct dentry *parent)
{

    trace_create_file("set_ftrace_filter", 0644, parent,
              ops, &ftrace_filter_fops);

    trace_create_file("set_ftrace_notrace", 0644, parent,
              ops, &ftrace_notrace_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,
};

“set_ftrace_filter”、“set_ftrace_notrace”的文件操作有个技巧,就是在open的时候分配一个临时hash表iter->hash来拷贝global_ops的filter_hash/notrace_hash的内容,在write操作实际设置filter时对iter->hash操作,在close的时候使用新的hash表iter->hash来更新global_ops。

open文件操作中对global_ops hash表的备份操作:

static int
ftrace_filter_open(struct inode *inode, struct file *file)
{
    /* (1) 得到global_ops结构 */
    struct ftrace_ops *ops = inode->i_private;

    /* (2) */
    return ftrace_regex_open(ops,
            FTRACE_ITER_FILTER | FTRACE_ITER_DO_HASH,
            inode, file);
}

↓

int
ftrace_regex_open(struct ftrace_ops *ops, int flag,
          struct inode *inode, struct file *file)
{
    struct ftrace_iterator *iter;
    struct ftrace_hash *hash;
    int ret = 0;

    ftrace_ops_init(ops);

    if (unlikely(ftrace_disabled))
        return -ENODEV;

    iter = kzalloc(sizeof(*iter), GFP_KERNEL);
    if (!iter)
        return -ENOMEM;

    if (trace_parser_get_init(&iter->parser, FTRACE_BUFF_MAX)) {
        kfree(iter);
        return -ENOMEM;
    }

    iter->ops = ops;
    iter->flags = flag;

    mutex_lock(&ops->func_hash->regex_lock);

    if (flag & FTRACE_ITER_NOTRACE)
        hash = ops->func_hash->notrace_hash;
    else
        hash = ops->func_hash->filter_hash;

    if (file->f_mode & FMODE_WRITE) {
        const int size_bits = FTRACE_HASH_DEFAULT_BITS;

        /* (2.1) 使用iter->hash备份global_ops中的filter_hash/notrace_hash */
        if (file->f_flags & O_TRUNC)
            iter->hash = alloc_ftrace_hash(size_bits);
        else
            iter->hash = alloc_and_copy_ftrace_hash(size_bits, hash);

        if (!iter->hash) {
            trace_parser_put(&iter->parser);
            kfree(iter);
            ret = -ENOMEM;
            goto out_unlock;
        }
    }

    if (file->f_mode & FMODE_READ) {
        iter->pg = ftrace_pages_start;

        ret = seq_open(file, &show_ftrace_seq_ops);
        if (!ret) {
            struct seq_file *m = file->private_data;
            m->private = iter;
        } else {
            /* Failed */
            free_ftrace_hash(iter->hash);
            trace_parser_put(&iter->parser);
            kfree(iter);
        }
    } else
        file->private_data = iter;

 out_unlock:
    mutex_unlock(&ops->func_hash->regex_lock);

    return ret;
}

write文件操作中对iter->hash表的更新操作:

ftrace_filter_write() -> ftrace_regex_write() 

↓

static ssize_t
ftrace_regex_write(struct file *file, const char __user *ubuf,
           size_t cnt, loff_t *ppos, int enable)
{
    struct ftrace_iterator *iter;
    struct trace_parser *parser;
    ssize_t ret, read;

    if (!cnt)
        return 0;

    if (file->f_mode & FMODE_READ) {
        struct seq_file *m = file->private_data;
        iter = m->private;
    } else
        iter = file->private_data;

    if (unlikely(ftrace_disabled))
        return -ENODEV;

    /* iter->hash is a local copy, so we don't need regex_lock */

    parser = &iter->parser;
    read = trace_get_user(parser, ubuf, cnt, ppos);

    if (read >= 0 && trace_parser_loaded(parser) &&
        !trace_parser_cont(parser)) {
        /* (1) 解析filter配置命令,配置到iter->hash中 */
        ret = ftrace_process_regex(iter->hash, parser->buffer,
                       parser->idx, enable);
        trace_parser_clear(parser);
        if (ret < 0)
            goto out;
    }

    ret = read;
 out:
    return ret;
}

↓

static int ftrace_process_regex(struct ftrace_hash *hash,
                char *buff, int len, int enable)
{
    char *func, *command, *next = buff;
    struct ftrace_func_command *p;
    int ret = -EINVAL;

    func = strsep(&next, ":");

    /* (1.1) filter配置,更新iter->hash */
    if (!next) {
        ret = ftrace_match_records(hash, func, len);
        if (!ret)
            ret = -EINVAL;
        if (ret < 0)
            return ret;
        return 0;
    }

    /* command found */

    command = strsep(&next, ":");

    /* (1.2) filter command配置,最后实际会操作到trace_probe_ops 
        和iter->hash、global_ops无关,下一小节中详述
    */
    mutex_lock(&ftrace_cmd_mutex);
    list_for_each_entry(p, &ftrace_commands, list) {
        if (strcmp(p->name, command) == 0) {
            ret = p->func(hash, func, command, next, enable);
            goto out_unlock;
        }
    }
 out_unlock:
    mutex_unlock(&ftrace_cmd_mutex);

    return ret;
}

↓

ftrace_match_records() -> match_records()

static int
match_records(struct ftrace_hash *hash, char *func, int len, char *mod)
{
    struct ftrace_page *pg;
    struct dyn_ftrace *rec;
    struct ftrace_glob func_g = { .type = MATCH_FULL };
    struct ftrace_glob mod_g = { .type = MATCH_FULL };
    struct ftrace_glob *mod_match = (mod) ? &mod_g : NULL;
    int exclude_mod = 0;
    int found = 0;
    int ret;
    int clear_filter;

    /* (1.1.1) 解析filter命令字符串 
        clear_filter = 命令中的“!”
     */
    if (func) {
        func_g.type = filter_parse_regex(func, len, &func_g.search,
                         &clear_filter);
        func_g.len = strlen(func_g.search);
    }

    if (mod) {
        mod_g.type = filter_parse_regex(mod, strlen(mod),
                &mod_g.search, &exclude_mod);
        mod_g.len = strlen(mod_g.search);
    }

    mutex_lock(&ftrace_lock);

    if (unlikely(ftrace_disabled))
        goto out_unlock;

    /* (1.1.2) 遍历ftrcae_rec */
    do_for_each_ftrace_rec(pg, rec) {
        /* 如果ip在filter中存在,将其加入/删除到iter->hash中 */
        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();
 out_unlock:
    mutex_unlock(&ftrace_lock);

    return found;
}

↓

static int
enter_record(struct ftrace_hash *hash, struct dyn_ftrace *rec, int clear_filter)
{
    struct ftrace_func_entry *entry;
    int ret = 0;

    entry = ftrace_lookup_ip(hash, rec->ip);
    if (clear_filter) {
        /* Do nothing if it doesn't exist */
        if (!entry)
            return 0;

        free_hash_entry(hash, entry);
    } else {
        /* Do nothing if it exists */
        if (entry)
            return 0;

        ret = add_hash_entry(hash, rec->ip);
    }
    return ret;
}

close文件操作中对global_ops hash表的更新操作:

int ftrace_regex_release(struct inode *inode, struct file *file)
{
    struct seq_file *m = (struct seq_file *)file->private_data;
    struct ftrace_ops_hash old_hash_ops;
    struct ftrace_iterator *iter;
    struct ftrace_hash **orig_hash;
    struct ftrace_hash *old_hash;
    struct trace_parser *parser;
    int filter_hash;
    int ret;

    if (file->f_mode & FMODE_READ) {
        iter = m->private;
        seq_release(inode, file);
    } else
        iter = file->private_data;

    parser = &iter->parser;
    if (trace_parser_loaded(parser)) {
        parser->buffer[parser->idx] = 0;
        ftrace_match_records(iter->hash, parser->buffer, parser->idx);
    }

    trace_parser_put(parser);

    mutex_lock(&iter->ops->func_hash->regex_lock);

    if (file->f_mode & FMODE_WRITE) {
        filter_hash = !!(iter->flags & FTRACE_ITER_FILTER);

        if (filter_hash)
            orig_hash = &iter->ops->func_hash->filter_hash;
        else
            orig_hash = &iter->ops->func_hash->notrace_hash;

        mutex_lock(&ftrace_lock);
        old_hash = *orig_hash;
        old_hash_ops.filter_hash = iter->ops->func_hash->filter_hash;
        old_hash_ops.notrace_hash = iter->ops->func_hash->notrace_hash;
        /* (1) 使用iter->hash来更新global_ops的filter_hash/notrace_hash */
        ret = ftrace_hash_move(iter->ops, filter_hash,
                       orig_hash, iter->hash);
        /* (2) 根据最新hash表的内容,更新_mcount插桩点 
            遍历全部ftrace_rec:
                ref_cnt大于0的插桩点,更新成ftrace_caller()
                ref_cnt等于0的插桩点,更新成nop
         */
        if (!ret) {
            ftrace_ops_update_code(iter->ops, &old_hash_ops);
            free_ftrace_hash_rcu(old_hash);
        }
        mutex_unlock(&ftrace_lock);
    }

    mutex_unlock(&iter->ops->func_hash->regex_lock);
    free_ftrace_hash(iter->hash);
    kfree(iter);

    return 0;
}

↓

static void ftrace_ops_update_code(struct ftrace_ops *ops,
                   struct ftrace_ops_hash *old_hash)
{
    struct ftrace_ops *op;

    if (!ftrace_enabled)
        return;

    if (ops->flags & FTRACE_OPS_FL_ENABLED) {
        ftrace_run_modify_code(ops, FTRACE_UPDATE_CALLS, old_hash);
        return;
    }

    /*
     * If this is the shared global_ops filter, then we need to
     * check if there is another ops that shares it, is enabled.
     * If so, we still need to run the modify code.
     */
    if (ops->func_hash != &global_ops.local_hash)
        return;

    do_for_each_ftrace_op(op, ftrace_ops_list) {
        if (op->func_hash == &global_ops.local_hash &&
            op->flags & FTRACE_OPS_FL_ENABLED) {
            ftrace_run_modify_code(op, FTRACE_UPDATE_CALLS, old_hash);
            /* Only need to do this once */
            return;
        }
    } while_for_each_ftrace_op(op);
}

1.2.7、配置function tracer的filter command

5、通过“set_ftrace_filter”设置function trcer的filter command。本质上向trace_probe_ops注册cmd,以及操作trace_probe_ops的filter_hash、notrace_hash。

虽然同样是操作set_ftrace_filter,但是配置filter和配置filter command是操作到不同的实体:

  • 配置filter。操作的是global_ops的filter_hash/notrace_hash的内容;
  • 配置filter command。是把command向trace_probe_ops注册,并且操作trace_probe_ops的filter_hash/notrace_hash的内容;

在配置filter command之前首先得注册command:

init_function_trace() -> init_func_cmd_traceon()

static int __init init_func_cmd_traceon(void)
{
    int ret;

    /* 注册:把command加入到ftrace_commands链表 */
    ret = register_ftrace_command(&ftrace_traceoff_cmd);
    if (ret)
        return ret;

    ret = register_ftrace_command(&ftrace_traceon_cmd);
    if (ret)
        goto out_free_traceoff;

    ret = register_ftrace_command(&ftrace_stacktrace_cmd);
    if (ret)
        goto out_free_traceon;

    ret = register_ftrace_command(&ftrace_dump_cmd);
    if (ret)
        goto out_free_stacktrace;

    ret = register_ftrace_command(&ftrace_cpudump_cmd);
    if (ret)
        goto out_free_dump;

    return 0;
}

static struct ftrace_func_command ftrace_traceon_cmd = {
    .name           = "traceon",
    .func           = ftrace_trace_onoff_callback,
};

我们以”traceon”command为例,继续分析上一节对“set_ftrace_filter”的文件操作:

static int ftrace_process_regex(struct ftrace_hash *hash,
                char *buff, int len, int enable)
{
    char *func, *command, *next = buff;
    struct ftrace_func_command *p;
    int ret = -EINVAL;

    func = strsep(&next, ":");

    /* (1.1) filter配置,更新iter->hash */
    if (!next) {
        ret = ftrace_match_records(hash, func, len);
        if (!ret)
            ret = -EINVAL;
        if (ret < 0)
            return ret;
        return 0;
    }

    /* command found */

    command = strsep(&next, ":");

    /* (1.2) filter command配置,最后实际会操作到trace_probe_ops 
        和iter->hash、global_ops无关,下一小节中详述
    */
    mutex_lock(&ftrace_cmd_mutex);
    list_for_each_entry(p, &ftrace_commands, list) {
        if (strcmp(p->name, command) == 0) {
            ret = p->func(hash, func, command, next, enable);
            goto out_unlock;
        }
    }
 out_unlock:
    mutex_unlock(&ftrace_cmd_mutex);

    return ret;
}

↓

static int
ftrace_trace_onoff_callback(struct ftrace_hash *hash,
                char *glob, char *cmd, char *param, int enable)
{
    struct ftrace_probe_ops *ops;

    /* we register both traceon and traceoff to this callback */
    /* (1.2.1) 配置command的执行ops */
    if (strcmp(cmd, "traceon") == 0)
        ops = param ? &traceon_count_probe_ops : &traceon_probe_ops;
    else
        ops = param ? &traceoff_count_probe_ops : &traceoff_probe_ops;

    /* (1.2.2) 注册command到trace_probe_ops */
    return ftrace_trace_probe_callback(ops, hash, glob, cmd,
                       param, enable);
}

↓

static int
ftrace_trace_probe_callback(struct ftrace_probe_ops *ops,
                struct ftrace_hash *hash, char *glob,
                char *cmd, char *param, int enable)
{
    void *count = (void *)-1;
    char *number;
    int ret;

    /* hash funcs only work with set_ftrace_filter */
    if (!enable)
        return -EINVAL;

    /* (1.2.2.1) 如果命令是“!”,注销filter command */
    if (glob[0] == '!') {
        unregister_ftrace_function_probe_func(glob+1, ops);
        return 0;
    }

    if (!param)
        goto out_reg;

    number = strsep(&param, ":");

    if (!strlen(number))
        goto out_reg;

    /*
     * We use the callback data field (which is a pointer)
     * as our counter.
     */
    /* (1.2.2.2) 解析到filter command中的“count”字段 */
    ret = kstrtoul(number, 0, (unsigned long *)&count);
    if (ret)
        return ret;

 out_reg:
    /* (1.2.2.3) 继续注册filter command */
    ret = register_ftrace_function_probe(glob, ops, count);

    return ret < 0 ? ret : 0;
}

↓

int
register_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops,
                  void *data)
{
    struct ftrace_ops_hash old_hash_ops;
    struct ftrace_func_probe *entry;
    struct ftrace_glob func_g;
    struct ftrace_hash **orig_hash = &trace_probe_ops.func_hash->filter_hash;
    struct ftrace_hash *old_hash = *orig_hash;
    struct ftrace_hash *hash;
    struct ftrace_page *pg;
    struct dyn_ftrace *rec;
    int not;
    unsigned long key;
    int count = 0;
    int ret;

    /* (1.2.2.3.1) filter command中函数名部分的解析 */
    func_g.type = filter_parse_regex(glob, strlen(glob),
            &func_g.search, &not);
    func_g.len = strlen(func_g.search);

    /* we do not support '!' for function probes */
    if (WARN_ON(not))
        return -EINVAL;

    mutex_lock(&trace_probe_ops.func_hash->regex_lock);

    old_hash_ops.filter_hash = old_hash;
    /* Probes only have filters */
    old_hash_ops.notrace_hash = NULL;

    /* (1.2.2.3.1) 将trace_probe_ops的filter_hash拷贝到临时hash表 */
    hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, old_hash);
    if (!hash) {
        count = -ENOMEM;
        goto out;
    }

    if (unlikely(ftrace_disabled)) {
        count = -ENODEV;
        goto out;
    }

    mutex_lock(&ftrace_lock);

    /* 遍历ftrace_rec */
    do_for_each_ftrace_rec(pg, rec) {

        if (!ftrace_match_record(rec, &func_g, NULL, 0))
            continue;

        entry = kmalloc(sizeof(*entry), GFP_KERNEL);
        if (!entry) {
            /* If we did not process any, then return error */
            if (!count)
                count = -ENOMEM;
            goto out_unlock;
        }

        count++;

        entry->data = data;

        /*
         * The caller might want to do something special
         * for each function we find. We call the callback
         * to give the caller an opportunity to do so.
         */
        if (ops->init) {
            if (ops->init(ops, rec->ip, &entry->data) < 0) {
                /* caller does not like this func */
                kfree(entry);
                continue;
            }
        }

        /* (1.2.2.3.3) 如果当前ip符合当前函数filter规则,加入到临时hash表 */
        ret = enter_record(hash, rec, 0);
        if (ret < 0) {
            kfree(entry);
            count = ret;
            goto out_unlock;
        }


        /* (1.2.2.3.4) 把command的操作和对应ip组合成entry,加入到另外一张hash表ftrace_func_hash */
        entry->ops = ops;
        entry->ip = rec->ip;

        key = hash_long(entry->ip, FTRACE_HASH_BITS);
        hlist_add_head_rcu(&entry->node, &ftrace_func_hash[key]);

    } while_for_each_ftrace_rec();

    /* (1.2.2.3.5) 把临时hash表更新到trace_probe_ops的filter_hash中 */
    ret = ftrace_hash_move(&trace_probe_ops, 1, orig_hash, hash);

    /* (1.2.2.3.6) 如果trace_probe_ops已经注册,根据hash表的更新来更新_mcount插桩点 
        如果trace_probe_ops没有注册,注册并更新_mcount插桩点
     */
    __enable_ftrace_function_probe(&old_hash_ops);

    if (!ret)
        free_ftrace_hash_rcu(old_hash);
    else
        count = ret;

 out_unlock:
    mutex_unlock(&ftrace_lock);
 out:
    mutex_unlock(&trace_probe_ops.func_hash->regex_lock);
    free_ftrace_hash(hash);

    return count;
}

trace_probe_ops在被调用的时候,执行ftrace_func_hash中的filter command:

ftrace_caller() -> ftrace_ops_list_func() -> __ftrace_ops_list_func() 

static inline void
__ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
               struct ftrace_ops *ignored, struct pt_regs *regs)
{
    struct ftrace_ops *op;
    int bit;

    bit = trace_test_and_set_recursion(TRACE_LIST_START, TRACE_LIST_MAX);
    if (bit < 0)
        return;

    /*
     * Some of the ops may be dynamically allocated,
     * they must be freed after a synchronize_sched().
     */
    preempt_disable_notrace();
    /* 遍历ftrace_ops_list链表,
        在当前ip满足hash的情况下,逐个执行ftrace_ops->func()
     */
    do_for_each_ftrace_op(op, ftrace_ops_list) {
        if (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);
out:
    preempt_enable_notrace();
    trace_clear_recursion(bit);
}

↓

static struct ftrace_ops trace_probe_ops __read_mostly =
{
    .func       = function_trace_probe_call,
    .flags      = FTRACE_OPS_FL_INITIALIZED,
    INIT_OPS_HASH(trace_probe_ops)
};

static void function_trace_probe_call(unsigned long ip, unsigned long parent_ip,
                      struct ftrace_ops *op, struct pt_regs *pt_regs)
{
    struct ftrace_func_probe *entry;
    struct hlist_head *hhd;
    unsigned long key;

    key = hash_long(ip, FTRACE_HASH_BITS);

    hhd = &ftrace_func_hash[key];

    if (hlist_empty(hhd))
        return;

    /*
     * Disable preemption for these calls to prevent a RCU grace
     * period. This syncs the hash iteration and freeing of items
     * on the hash. rcu_read_lock is too dangerous here.
     */
    preempt_disable_notrace();
    /* 逐个执行ftrace_func_hash表中,ip为本ip的filter command */
    hlist_for_each_entry_rcu_notrace(entry, hhd, node) {
        if (entry->ip == ip)
            entry->ops->func(ip, parent_ip, &entry->data);
    }
    preempt_enable_notrace();
}

1.3、数据存入

function tracer的数据存入的路径为:

ftrace_caller() -> ftrace_ops_list_func() -> __ftrace_ops_list_func() -> global_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;

    pc = preempt_count();
    preempt_disable_notrace();

    bit = trace_test_and_set_recursion(TRACE_FTRACE_START, TRACE_FTRACE_MAX);
    if (bit < 0)
        goto out;

    cpu = smp_processor_id();
    data = per_cpu_ptr(tr->trace_buffer.data, cpu);
    if (!atomic_read(&data->disabled)) {
        local_save_flags(flags);
        trace_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;

    /* (1) 从ringbuffer中分配空间 
        type = TRACE_FN
     */
    event = trace_buffer_lock_reserve(buffer, TRACE_FN, sizeof(*entry),
                      flags, pc);
    if (!event)
        return;
    entry   = ring_buffer_event_data(event);
    /* (2) 存入function tracer自定义的trace数据:ip、parent_ip */
    entry->ip           = ip;
    entry->parent_ip        = parent_ip;

    if (!call_filter_check_discard(call, entry, buffer, event))
        __buffer_unlock_commit(buffer, event);
}

1.3.1、数据格式

function tracer自定义的trace数据非常简单:ip、parent_ip

这里写图片描述

1.3.2、filter

关于filter和filter command的配置在1.2.2、插桩点的动态管理一节已经讲得非常详细了,这里就不再重复。

1.4、数据读出

从trace文件读出的function tracer默认数据格式为:

# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 36/36   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
              sh-18361 [001] ...1 663922.156238: schedule_hrtimeout_range <-poll_schedule_timeout
              sh-18361 [001] ...1 663922.156251: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [003] .n.2 663922.156660: schedule_preempt_disabled <-cpu_startup_entry
     ->transport-5191  [002] ...1 663922.157592: schedule_timeout <-wait_for_common
            adbd-5189  [002] ...1 663922.158219: schedule_hrtimeout_range <-poll_schedule_timeout
            adbd-5189  [002] ...1 663922.158222: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [001] .n.2 663922.158342: schedule_preempt_disabled <-cpu_startup_entry
     ->transport-5191  [001] ...1 663922.159407: schedule_timeout <-unix_stream_read_generic
     <-transport-5192  [001] ...1 663922.159904: schedule_timeout <-wait_for_common
     ->transport-5191  [001] ...1 663922.160413: schedule_timeout <-unix_stream_read_generic
            adbd-5189  [001] ...1 663922.160895: schedule_hrtimeout_range <-poll_schedule_timeout
            adbd-5189  [001] ...1 663922.160898: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [002] .n.2 663922.163694: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [003] .n.2 663922.175958: schedule_preempt_disabled <-cpu_startup_entry
  ndroid.systemu-2200  [003] ...1 663922.179945: schedule_hrtimeout_range <-SyS_epoll_wait
  ndroid.systemu-2200  [003] ...1 663922.179950: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [003] .n.2 663922.181418: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [002] .n.2 663922.181584: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [002] .n.2 663922.200878: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [001] .n.2 663922.203506: schedule_preempt_disabled <-cpu_startup_entry
     rcu_preempt-7     [002] ...1 663922.203519: schedule_timeout <-rcu_gp_kthread
  ndroid.systemu-2200  [002] ...1 663922.205619: schedule_hrtimeout_range <-SyS_epoll_wait
  ndroid.systemu-2200  [002] ...1 663922.205624: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [003] .n.2 663922.208538: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [002] .n.2 663922.213243: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [003] .n.2 663922.217986: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [003] .n.2 663922.227857: schedule_preempt_disabled <-cpu_startup_entry
  ndroid.systemu-2200  [003] ...1 663922.231819: schedule_hrtimeout_range <-SyS_epoll_wait
  ndroid.systemu-2200  [003] ...1 663922.231824: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [002] .n.2 663922.234282: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [001] .n.2 663922.246355: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [002] .n.2 663922.251360: schedule_preempt_disabled <-cpu_startup_entry
  ndroid.systemu-2200  [002] ...1 663922.255168: schedule_hrtimeout_range <-SyS_epoll_wait
  ndroid.systemu-2200  [002] ...1 663922.255172: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
          <idle>-0     [003] .n.2 663922.256775: schedule_preempt_disabled <-cpu_startup_entry
          <idle>-0     [001] .n.2 663922.257253: schedule_preempt_disabled <-cpu_startup_entry

在kernel/trace/trace_ouput.c文件中,注册了系统默认的几种trace_event。function tracer使用TRACE_FN类型的trace_fn_event:

static struct trace_event *events[] __initdata = {
    &trace_fn_event,
    &trace_graph_ent_event,
    &trace_graph_ret_event,
    &trace_ctx_event,
    &trace_wake_event,
    &trace_stack_event,
    &trace_user_stack_event,
    &trace_bputs_event,
    &trace_bprint_event,
    &trace_print_event,
    NULL
};

__init static int init_events(void)
{
    struct trace_event *event;
    int i, ret;

    for (i = 0; events[i]; i++) {
        event = events[i];

        ret = register_trace_event(event);
        if (!ret) {
            printk(KERN_WARNING "event %d failed to register\n",
                   event->type);
            WARN_ON_ONCE(1);
        }
    }

    return 0;
}

|→

static struct trace_event_functions trace_fn_funcs = {
    .trace      = trace_fn_trace,
    .raw        = trace_fn_raw,
    .hex        = trace_fn_hex,
    .binary     = trace_fn_bin,
};

static struct trace_event trace_fn_event = {
    .type       = TRACE_FN,
    .funcs      = &trace_fn_funcs,
};

在数据读出时,会调用到event对应的event->funcs->trace()函数,seq_read() -> s_show() -> print_trace_line() -> print_trace_fmt() -> event->funcs->trace():

TRACE_FN,event->funcs->trace()对应trace_fn_trace():

/* TRACE_FN */
static enum print_line_t trace_fn_trace(struct trace_iterator *iter, int flags,
                    struct trace_event *event)
{
    struct ftrace_entry *field;
    struct trace_seq *s = &iter->seq;

    trace_assign_type(field, iter->ent);

    /* (1) 打印出本ip对应的符号 */
    seq_print_ip_sym(s, field->ip, flags);

    /* (2) 如果trace option运行,打印出父ip对应的符号 */
    if ((flags & TRACE_ITER_PRINT_PARENT) && field->parent_ip) {
        trace_seq_puts(s, " <-");
        seq_print_ip_sym(s, field->parent_ip, flags);
    }

    trace_seq_putc(s, '\n');

    return trace_handle_return(s);
}

2、function_graph tracer

function_graph tracer从function tracer发展而来,function tracer使用“_mcount”插桩可以跟踪到每个函数的调用入口,而function_graph tracer即可以跟踪到函数的入口还可以跟踪到函数的返回。

2.1、插桩原理

这里写图片描述

如上图:一切的关键是在入口桩函数被调用时,修改了func()的返回地址,不是返回到func’s parent()函数继续去执行,而是返回到reurn桩函数return_to_handler()中。return_to_handler()中执行完自己的return处理函数以后,再把返回地址恢复成func’s parent中的地址,返回继续执行原有的路径。

原本的入口处插桩,只能追踪到函数的切换。现在入口、出口同时插桩,还能获得函数的执行时长,做更多的分析。

2.2、插桩点管理

这里写图片描述

function_graph tracer插桩点动态管理的原理:

  • 1、每个“bl _mcount”的插桩点ip对应一个每个插桩点对应一个dyn_ftrace结构。dyn_ftrace->flags的低26bit用来表示引用计数,如果有ftrcae_ops会操作到该ip,引用计数会加1。如果ref_cnt大于0,插桩点就需要使能了,把其替换为“bl ftrace_caller”;
  • 2、ftrcae_ops采用hash表来表达对ip的引用,一个ftrcae_ops有两张hash表filter_hash和notrace_hash,综合的结果就是对ip的引用。function_graph tracer模式时,有一个的ftrcae_ops:graph_ops。并且graph_ops的hash表是直接引用global_ops的hash表,这样做的目的是可以保留function tracer模式时的filter配置。如果引用到ip,都会造成ip的1级插桩点被替换为“bl ftrace_caller”;
  • 3、通过设置“set_ftrace_filter/set_ftrace_notrace”会造成对global_ops的filter_hash/notrace_hash的配置,它的作用相当于1级过滤。在function_graph tracer模式时,通过2级过滤的函数才能记录trace信息:1级过滤(函数必须是“set_ftrace_filter/set_ftrace_notrace”配置中允许的函数),2级过滤(函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数);
  • 4、通过设置“set_graph_function/set_graph_notrace”可以配置2级过滤,实际上是配置到ftrace_graph_funcs[]/ftrace_graph_notrace_funcs[]表中。
  • 5、ftrace_caller()有两个2级插桩点:ftrace_call、ftrace_graph_call。在function tracer模式时,只有ftrace_graph_call被设置成“bl ftrace_graph_caller”;
  • 6、ftrace_graph_caller()的工作就是设置环境,让函数在入口处调用trace_graph_entry(),在返回处调用trace_graph_return();
  • 7、在trace_graph_entry()中会进行2级过滤的合法性判断,函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数才能继续执行,否则出错返回后面的trace_graph_return()都不会被执行。
  • 8、trace_graph_entry()、trace_graph_return()的工作都是记录trace信息:ip+时间戳;

在以下场景时,function_graph会涉及到对各个插桩点的动态修改:

  • tracer的使能。当使用“echo xxx_tracer > current_tracer”时,会关闭旧的current tracer并使能新的tracer。典型的包括function tracer合入function_graph tracer;
  • function_graph filter的配置。使用“echo function_name > set_ftrace_filter/set_ftrace_notrace”,可以配置部分function被trace,而不是所有function被trace;
  • function filter的配置。使用“echo function_name > set_graph_function/set_graph_notrace”,可以配置部分function被trace,而不是所有function被trace;

下面我们看看具体场景下的代码实现细节:

2.2.1、function_graph tracer使能

参考“1.2.5、function tracer使能”这一节,tracer的使能最后调用的是tracer->init()函数。

对function_graph tracer来说,调用的是graph_trace_init():

static struct tracer graph_trace __tracer_data = {
    .name       = "function_graph",
    .update_thresh  = graph_trace_update_thresh,
    .open       = graph_trace_open,
    .pipe_open  = graph_trace_open,
    .close      = graph_trace_close,
    .pipe_close = graph_trace_close,
    .init       = graph_trace_init,
    .reset      = graph_trace_reset,
    .print_line = print_graph_function,
    .print_header   = print_graph_headers,
    .flags      = &tracer_flags,
    .set_flag   = func_graph_set_flag,
#ifdef CONFIG_FTRACE_SELFTEST
    .selftest   = trace_selftest_startup_function_graph,
#endif
};

↓

static int graph_trace_init(struct trace_array *tr)
{
    int ret;

    set_graph_array(tr);
    if (tracing_thresh)
        ret = register_ftrace_graph(&trace_graph_thresh_return,
                        &trace_graph_thresh_entry);
    else
        ret = register_ftrace_graph(&trace_graph_return,
                        &trace_graph_entry);
    if (ret)
        return ret;
    tracing_start_cmdline_record();

    return 0;
}

↓

/* function_graph tracer的专用ftrace_ops:graph_ops */
static struct ftrace_ops graph_ops = {
    .func           = ftrace_stub,
    .flags          = FTRACE_OPS_FL_RECURSION_SAFE |
                   FTRACE_OPS_FL_INITIALIZED |
                   FTRACE_OPS_FL_PID |
                   FTRACE_OPS_FL_STUB,
#ifdef FTRACE_GRAPH_TRAMP_ADDR
    .trampoline     = FTRACE_GRAPH_TRAMP_ADDR,
    /* trampoline_size is only needed for dynamically allocated tramps */
#endif
    ASSIGN_OPS_HASH(graph_ops, &global_ops.local_hash)  /* graph_ops共用global_ops的hash表,这样可以支持"set_ftrace_filter"的配置 */
};


int register_ftrace_graph(trace_func_graph_ret_t retfunc,
            trace_func_graph_ent_t entryfunc)
{
    int ret = 0;

    mutex_lock(&ftrace_lock);

    /* we currently allow only one tracer registered at a time */
    if (ftrace_graph_active) {
        ret = -EBUSY;
        goto out;
    }

    register_pm_notifier(&ftrace_suspend_notifier);

    ftrace_graph_active++;
    ret = start_graph_tracing();
    if (ret) {
        ftrace_graph_active--;
        goto out;
    }

    /* (1) 给ftrace_graph_entry、ftrace_graph_return指针赋值 */
    ftrace_graph_return = retfunc;

    /*
     * Update the indirect function to the entryfunc, and the
     * function that gets called to the entry_test first. Then
     * call the update fgraph entry function to determine if
     * the entryfunc should be called directly or not.
     */
    __ftrace_graph_entry = entryfunc;
    ftrace_graph_entry = ftrace_graph_entry_test;
    update_function_graph_func();

    /* (2) 注册graph_ops:
        1、将graph_ops加入到ftrace_ops_list链表;
        2、根据graph_ops的hash表,更新_mcount插桩点;
        3、更新ftrace_graph_call插桩点为ftrace_graph_caller()
     */
    ret = ftrace_startup(&graph_ops, FTRACE_START_FUNC_RET);
out:
    mutex_unlock(&ftrace_lock);
    return ret;
}

2.2.2、配置function filter(1级过滤)

因为function_graph tracer的graph_ops继续共用global_ops的hash表,使用“set_ftrace_filter/set_ftrace_notrace”接口可以配置global_ops的filter_hash/notrace_hash表。所以可以继续使用“set_ftrace_filter/set_ftrace_notrace”来配置function_graph tracer的filter。

function_graph tracer还可以使用“set_graph_function/set_graph_notrace”接口来配置过滤,需要两种过滤条件都满足的函数才能被trace。所以我们命名当前过滤为1级过滤。

具体的代码解析参考: 1.2.6、配置function tracer的filter

2.2.3、配置function_graph filter(2级过滤)

通过设置“set_graph_function/set_graph_notrace”可以配置2级过滤,实际上是配置到ftrace_graph_funcs[]/ftrace_graph_notrace_funcs[]表中。

trace_create_file("set_graph_function", 0444, d_tracer,
                NULL,
                &ftrace_graph_fops);
trace_create_file("set_graph_notrace", 0444, d_tracer,
                NULL,
                &ftrace_graph_notrace_fops);

static const struct file_operations ftrace_graph_fops = {
    .open       = ftrace_graph_open,
    .read       = seq_read,
    .write      = ftrace_graph_write,
    .llseek     = tracing_lseek,
    .release    = ftrace_graph_release,
};

↓

static int
ftrace_graph_open(struct inode *inode, struct file *file)
{
    struct ftrace_graph_data *fgd;

    if (unlikely(ftrace_disabled))
        return -ENODEV;

    fgd = kmalloc(sizeof(*fgd), GFP_KERNEL);
    if (fgd == NULL)
        return -ENOMEM;

    fgd->table = ftrace_graph_funcs;
    fgd->size = FTRACE_GRAPH_MAX_FUNCS;
    fgd->count = &ftrace_graph_count;
    fgd->seq_ops = &ftrace_graph_seq_ops;

    return __ftrace_graph_open(inode, file, fgd);
}


static ssize_t
ftrace_graph_write(struct file *file, const char __user *ubuf,
           size_t cnt, loff_t *ppos)
{
    struct trace_parser parser;
    ssize_t read, ret = 0;
    struct ftrace_graph_data *fgd = file->private_data;

    if (!cnt)
        return 0;

    if (trace_parser_get_init(&parser, FTRACE_BUFF_MAX))
        return -ENOMEM;

    read = trace_get_user(&parser, ubuf, cnt, ppos);

    if (read >= 0 && trace_parser_loaded((&parser))) {
        parser.buffer[parser.idx] = 0;

        mutex_lock(&graph_lock);

        /* we allow only one expression at a time */
        /* 根据filter设置条件,设置ftrace_graph_funcs[]表 */
        ret = ftrace_set_func(fgd->table, fgd->count, fgd->size,
                      parser.buffer);

        mutex_unlock(&graph_lock);
    }

    if (!ret)
        ret = read;

    trace_parser_put(&parser);

    return ret;
}

在trace_graph_entry()中会进行2级过滤的合法性判断,函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数才能继续执行,否则出错返回后面的trace_graph_return()都不会被执行。

int trace_graph_entry(struct ftrace_graph_ent *trace)
{
    struct trace_array *tr = graph_array;
    struct trace_array_cpu *data;
    unsigned long flags;
    long disabled;
    int ret;
    int cpu;
    int pc;

    if (!ftrace_trace_task(current))
        return 0;

    /* trace it when it is-nested-in or is a function enabled. */
    /*  ftrace_graph_addr(trace->func) // 函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数
        trace->depth // 或者是被它们调用的子函数
     */
    if ((!(trace->depth || ftrace_graph_addr(trace->func)) ||
         ftrace_graph_ignore_irqs()) || (trace->depth < 0) ||
        (max_depth && trace->depth >= max_depth))
        return 0;

    /*
     * Do not trace a function if it's filtered by set_graph_notrace.
     * Make the index of ret stack negative to indicate that it should
     * ignore further functions.  But it needs its own ret stack entry
     * to recover the original index in order to continue tracing after
     * returning from the function.
     */
    if (ftrace_graph_notrace_addr(trace->func))
        return 1;

    local_irq_save(flags);
    cpu = raw_smp_processor_id();
    data = per_cpu_ptr(tr->trace_buffer.data, cpu);
    disabled = atomic_inc_return(&data->disabled);
    if (likely(disabled == 1)) {
        pc = preempt_count();
        ret = __trace_graph_entry(tr, trace, flags, pc);
    } else {
        ret = 0;
    }

    atomic_dec(&data->disabled);
    local_irq_restore(flags);

    return ret;
}

↓

static inline int ftrace_graph_addr(unsigned long addr)
{
    int i;

    if (!ftrace_graph_count)
        return 1;

    /* 查找地址是否在ftrace_graph_funcs[]表中 */
    for (i = 0; i < ftrace_graph_count; i++) {
        if (addr == ftrace_graph_funcs[i]) {
            /*
             * If no irqs are to be traced, but a set_graph_function
             * is set, and called by an interrupt handler, we still
             * want to trace it.
             */
            if (in_irq())
                trace_recursion_set(TRACE_IRQ_BIT);
            else
                trace_recursion_clear(TRACE_IRQ_BIT);
            return 1;
        }
    }

    return 0;
}

2.3、数据存入

和function tracer不一样的是,function_graph在进入函数和返回函数时都有trace数据存入。

2.3.1、trace_graph_entry()

这里写图片描述

函数入口的存入:trace_graph_entry() -> __trace_graph_entry():

int __trace_graph_entry(struct trace_array *tr,
                struct ftrace_graph_ent *trace,
                unsigned long flags,
                int pc)
{
    struct trace_event_call *call = &event_funcgraph_entry;
    struct ring_buffer_event *event;
    struct ring_buffer *buffer = tr->trace_buffer.buffer;
    struct ftrace_graph_ent_entry *entry;

    event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_ENT,
                      sizeof(*entry), flags, pc);
    if (!event)
        return 0;
    entry   = ring_buffer_event_data(event);
    entry->graph_ent            = *trace;
    if (!call_filter_check_discard(call, entry, buffer, event))
        __buffer_unlock_commit(buffer, event);

    return 1;
}

2.3.2、trace_graph_return()

这里写图片描述

函数返回的存入:trace_graph_return() -> __trace_graph_return():

void __trace_graph_return(struct trace_array *tr,
                struct ftrace_graph_ret *trace,
                unsigned long flags,
                int pc)
{
    struct trace_event_call *call = &event_funcgraph_exit;
    struct ring_buffer_event *event;
    struct ring_buffer *buffer = tr->trace_buffer.buffer;
    struct ftrace_graph_ret_entry *entry;

    event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RET,
                      sizeof(*entry), flags, pc);
    if (!event)
        return;
    entry   = ring_buffer_event_data(event);
    entry->ret              = *trace;
    if (!call_filter_check_discard(call, entry, buffer, event))
        __buffer_unlock_commit(buffer, event);
}

2.3.3、filter

参见“2.2.2、配置function filter(1级过滤)”和“2.2.3、配置function_graph filter(2级过滤)”。

2.4、数据读出

从trace文件读出的function_graph tracer默认数据格式为:(显示function_graph中设置的函数或者是被它们调用的子函数)

# cat trace
# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 0)               |  tty_open() {
 0)   1.563 us    |    nonseekable_open();
 0)               |    tty_alloc_file() {
 0)               |      kmem_cache_alloc_trace() {
 0)               |        __might_sleep() {
 0)   0.417 us    |          ___might_sleep();
 0)   4.791 us    |        }
 0)   1.302 us    |        preempt_count_add();
 0)   1.250 us    |        preempt_count_sub();
 0)               |        __slab_alloc.isra.60.constprop.62() {
 0)               |          ___slab_alloc.constprop.63() {
 0)               |            _raw_spin_lock() {
 0)   1.250 us    |              preempt_count_add();
 0)   0.520 us    |              do_raw_spin_trylock();
 0)   9.531 us    |            }
 0)   0.781 us    |            preempt_count_add();
 0)   0.469 us    |            preempt_count_sub();
 0)               |            _raw_spin_unlock() {
 0)   0.521 us    |              do_raw_spin_unlock();
 0)   1.614 us    |              preempt_count_sub();
 0)   9.584 us    |            }
 0)               |            alloc_debug_processing() {
 0)               |              check_slab() {
 0)   1.198 us    |                slab_pad_check.part.52();
 0)   5.990 us    |              }
 0)               |              check_object() {
 0)   1.719 us    |                check_bytes_and_report();
 0)   0.521 us    |                check_bytes_and_report();
 0)   2.448 us    |                check_bytes_and_report();
 0)   0.469 us    |                check_bytes_and_report();
 0)   1.927 us    |                check_bytes_and_report();
 0) + 29.843 us   |              }
 0)               |              set_track() {

在通过trace文件读出数据时,如果tracer的print_line()函数有定义,则会调用print_line()函数进行解析。而不再去使用event->funcs->trace()函数。

1、读操作时,seq_read() -> s_show() -> print_trace_line() -> tracer->print_line():

static struct tracer graph_trace __tracer_data = {
    .name       = "function_graph",
    .update_thresh  = graph_trace_update_thresh,
    .open       = graph_trace_open,
    .pipe_open  = graph_trace_open,
    .close      = graph_trace_close,
    .pipe_close = graph_trace_close,
    .init       = graph_trace_init,
    .reset      = graph_trace_reset,
    .print_line = print_graph_function,
    .print_header   = print_graph_headers,
    .flags      = &tracer_flags,
    .set_flag   = func_graph_set_flag,
#ifdef CONFIG_FTRACE_SELFTEST
    .selftest   = trace_selftest_startup_function_graph,
#endif
};

↓

static enum print_line_t
print_graph_function(struct trace_iterator *iter)
{
    return print_graph_function_flags(iter, tracer_flags.val);
}

↓

enum print_line_t
print_graph_function_flags(struct trace_iterator *iter, u32 flags)
{
    struct ftrace_graph_ent_entry *field;
    struct fgraph_data *data = iter->private;
    struct trace_entry *entry = iter->ent;
    struct trace_seq *s = &iter->seq;
    int cpu = iter->cpu;
    int ret;

    if (flags & TRACE_GRAPH_PRINT_FLAT)
        return TRACE_TYPE_UNHANDLED;

    if (data && per_cpu_ptr(data->cpu_data, cpu)->ignore) {
        per_cpu_ptr(data->cpu_data, cpu)->ignore = 0;
        return TRACE_TYPE_HANDLED;
    }

    /*
     * If the last output failed, there's a possibility we need
     * to print out the missing entry which would never go out.
     */
    if (data && data->failed) {
        field = &data->ent;
        iter->cpu = data->cpu;
        ret = print_graph_entry(field, s, iter, flags);
        if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
            per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1;
            ret = TRACE_TYPE_NO_CONSUME;
        }
        iter->cpu = cpu;
        return ret;
    }

    switch (entry->type) {
    /* (1) 打印TRACE_GRAPH_ENT类型的entry */
    case TRACE_GRAPH_ENT: {
        /*
         * print_graph_entry() may consume the current event,
         * thus @field may become invalid, so we need to save it.
         * sizeof(struct ftrace_graph_ent_entry) is very small,
         * it can be safely saved at the stack.
         */
        struct ftrace_graph_ent_entry saved;
        trace_assign_type(field, entry);
        saved = *field;
        return print_graph_entry(&saved, s, iter, flags);
    }
    /* (2) 打印TRACE_GRAPH_RET类型的entry */
    case TRACE_GRAPH_RET: {
        struct ftrace_graph_ret_entry *field;
        trace_assign_type(field, entry);
        return print_graph_return(&field->ret, s, entry, iter, flags);
    }

    /* (3) TRACE_STACK、TRACE_FN这两种类型的entry,graph无法处理,返回交由系统打印 */
    case TRACE_STACK:
    case TRACE_FN:
        /* dont trace stack and functions as comments */
        return TRACE_TYPE_UNHANDLED;

    /* (4) 打印其他类型的entry */
    default:
        return print_graph_comment(s, entry, iter, flags);
    }

    return TRACE_TYPE_HANDLED;
}

2、在第一次读操作时,会打印header。seq_read() -> s_show() -> print_trace_line() -> tracer->print_header():

static void print_graph_headers(struct seq_file *s)
{
    print_graph_headers_flags(s, tracer_flags.val);
}

↓

void print_graph_headers_flags(struct seq_file *s, u32 flags)
{
    struct trace_iterator *iter = s->private;
    struct trace_array *tr = iter->tr;

    if (flags & TRACE_GRAPH_PRINT_FLAT) {
        trace_default_header(s);
        return;
    }

    if (!(tr->trace_flags & TRACE_ITER_CONTEXT_INFO))
        return;

    if (tr->trace_flags & TRACE_ITER_LATENCY_FMT) {
        /* print nothing if the buffers are empty */
        if (trace_empty(iter))
            return;

        print_trace_header(s, iter);
    }

    __print_graph_headers_flags(tr, s, flags);
}

3、在open的时候,也会调用到tracer的open函数。tracing_open() -> __tracing_open() -> tracer->open():

void graph_trace_open(struct trace_iterator *iter)
{
    /* pid and depth on the last trace processed */
    struct fgraph_data *data;
    gfp_t gfpflags;
    int cpu;

    iter->private = NULL;

    /* We can be called in atomic context via ftrace_dump() */
    gfpflags = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL;

    data = kzalloc(sizeof(*data), gfpflags);
    if (!data)
        goto out_err;

    data->cpu_data = alloc_percpu_gfp(struct fgraph_cpu_data, gfpflags);
    if (!data->cpu_data)
        goto out_err_free;

    for_each_possible_cpu(cpu) {
        pid_t *pid = &(per_cpu_ptr(data->cpu_data, cpu)->last_pid);
        int *depth = &(per_cpu_ptr(data->cpu_data, cpu)->depth);
        int *ignore = &(per_cpu_ptr(data->cpu_data, cpu)->ignore);
        int *depth_irq = &(per_cpu_ptr(data->cpu_data, cpu)->depth_irq);

        *pid = -1;
        *depth = 0;
        *ignore = 0;
        *depth_irq = -1;
    }

    iter->private = data;

    return;

 out_err_free:
    kfree(data);
 out_err:
    pr_warning("function graph tracer: not enough memory\n");
}

3、irqsoff tracer

irqsoff tracer用来追踪最大关中断时间。它的trace会提供几部分信息:

  • 1、irqoff的最大时长:latency;
  • 2、在最大irqoff这期间所有的function trace信息;
  • 3、最后的irqon的函数回调信息;

3.1、插桩

这里写图片描述

irqsoff tracer的插桩方法,是直接在local_irq_enable()、local_irq_disable()中直接插入钩子函数trace_hardirqs_on()、trace_hardirqs_off()。

#ifdef CONFIG_TRACE_IRQFLAGS
#define local_irq_enable() \
    do { trace_hardirqs_on(); raw_local_irq_enable(); } while (0)
#define local_irq_disable() \
    do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
#define local_irq_save(flags)               \
    do {                        \
        raw_local_irq_save(flags);      \
        trace_hardirqs_off();           \
    } while (0)


#define local_irq_restore(flags)            \
    do {                        \
        if (raw_irqs_disabled_flags(flags)) {   \
            raw_local_irq_restore(flags);   \
            trace_hardirqs_off();       \
        } else {                \
            trace_hardirqs_on();        \
            raw_local_irq_restore(flags);   \
        }                   \
    } while (0)

#define safe_halt()             \
    do {                    \
        trace_hardirqs_on();        \
        raw_safe_halt();        \
    } while (0)


#else /* !CONFIG_TRACE_IRQFLAGS */

3.1.1、trace_hardirqs_on/off()

local_irq_disable() -> trace_hardirqs_off():

void trace_hardirqs_off(void)
{
    if (!preempt_trace() && irq_trace())
        start_critical_timing(CALLER_ADDR0, CALLER_ADDR1);
}

↓

static inline void
start_critical_timing(unsigned long ip, unsigned long parent_ip)
{
    int cpu;
    struct trace_array *tr = irqsoff_trace;
    struct trace_array_cpu *data;
    unsigned long flags;

    if (!tracer_enabled || !tracing_is_enabled())
        return;

    cpu = raw_smp_processor_id();

    /* (1) 如果已经开始tracing,中间的重复关中断操作被忽略 */
    if (per_cpu(tracing_cpu, cpu))
        return;

    data = per_cpu_ptr(tr->trace_buffer.data, cpu);

    if (unlikely(!data) || atomic_read(&data->disabled))
        return;

    atomic_inc(&data->disabled);

    /* (2) 记录trace的开始时间 */
    data->critical_sequence = max_sequence;
    data->preempt_timestamp = ftrace_now(cpu);
    data->critical_start = parent_ip ? : ip;

    local_save_flags(flags);

    /* (3) 记录一下当前的function trace信息 */
    __trace_function(tr, ip, parent_ip, flags, preempt_count());

    /* (4) 设置开始tracing标志 
        这是一个非常重要的标准,打开所有function trace的记录
     */
    per_cpu(tracing_cpu, cpu) = 1;

    atomic_dec(&data->disabled);
}

local_irq_enable() -> trace_hardirqs_on():

void trace_hardirqs_on(void)
{
    if (!preempt_trace() && irq_trace())
        stop_critical_timing(CALLER_ADDR0, CALLER_ADDR1);
}

↓

static inline void
stop_critical_timing(unsigned long ip, unsigned long parent_ip)
{
    int cpu;
    struct trace_array *tr = irqsoff_trace;
    struct trace_array_cpu *data;
    unsigned long flags;

    cpu = raw_smp_processor_id();
    /* Always clear the tracing cpu on stopping the trace */
    /* (1) 清除开始tracing标志 
        这是一个非常重要的标准,停止所有function trace的记录
     */
    if (unlikely(per_cpu(tracing_cpu, cpu)))
        per_cpu(tracing_cpu, cpu) = 0;
    else
        return;

    if (!tracer_enabled || !tracing_is_enabled())
        return;

    data = per_cpu_ptr(tr->trace_buffer.data, cpu);

    if (unlikely(!data) ||
        !data->critical_start || atomic_read(&data->disabled))
        return;

    atomic_inc(&data->disabled);

    /* (2) 记录一下当前的function trace信息 */
    local_save_flags(flags);
    __trace_function(tr, ip, parent_ip, flags, preempt_count());

    /* (3) check当前的irqoff持续时长是不是最大的latency */
    check_critical_timing(tr, data, parent_ip ? : ip, cpu);
    data->critical_start = 0;
    atomic_dec(&data->disabled);
}

↓

static void
check_critical_timing(struct trace_array *tr,
              struct trace_array_cpu *data,
              unsigned long parent_ip,
              int cpu)
{
    cycle_t T0, T1, delta;
    unsigned long flags;
    int pc;

    /* (3.1) 当前irqoff的持续时长 */
    T0 = data->preempt_timestamp;
    T1 = ftrace_now(cpu);
    delta = T1-T0;

    local_save_flags(flags);

    pc = preempt_count();

    /* (3.2) 判断是否是新的最大latency */
    if (!report_latency(tr, delta))
        goto out;

    raw_spin_lock_irqsave(&max_trace_lock, flags);

    /* check if we are still the max latency */
    if (!report_latency(tr, delta))
        goto out_unlock;

    /* (3.3) 如果是最大latency: 
        记录一下当前的function trace信息
        记录当前的stack信息
     */
    __trace_function(tr, CALLER_ADDR0, parent_ip, flags, pc);
    /* Skip 5 functions to get to the irq/preempt enable function */
    __trace_stack(tr, flags, 5, pc);

    if (data->critical_sequence != max_sequence)
        goto out_unlock;

    data->critical_end = parent_ip;

    /* (3.4) ---------重要----------------- 
        这一步非常重要,主要做了以下工作:
        1、把当前的trace buffer备份到max_buffer中
        2、更新max_buffer中max_buf->time_start时间戳,这个记录了最大latency trace信息的起始位置
     */
    if (likely(!is_tracing_stopped())) {
        tr->max_latency = delta;
        update_max_tr_single(tr, current, cpu);
    }

    max_sequence++;

out_unlock:
    raw_spin_unlock_irqrestore(&max_trace_lock, flags);

out:
    data->critical_sequence = max_sequence;
    data->preempt_timestamp = ftrace_now(cpu);
    __trace_function(tr, CALLER_ADDR0, parent_ip, flags, pc);
}

↓

void
update_max_tr_single(struct trace_array *tr, struct task_struct *tsk, int cpu)
{
    int ret;

    if (tr->stop_count)
        return;

    WARN_ON_ONCE(!irqs_disabled());
    if (!tr->allocated_snapshot) {
        /* Only the nop tracer should hit this when disabling */
        WARN_ON_ONCE(tr->current_trace != &nop_trace);
        return;
    }

    arch_spin_lock(&tr->max_lock);

    /* (3.4.1) swap trace_buffer和max_buffer,把包含最大latency过程的trace信息备份到max_buffer中 */
    ret = ring_buffer_swap_cpu(tr->max_buffer.buffer, tr->trace_buffer.buffer, cpu);

    if (ret == -EBUSY) {
        /*
         * We failed to swap the buffer due to a commit taking
         * place on this CPU. We fail to record, but we reset
         * the max trace buffer (no one writes directly to it)
         * and flag that it failed.
         */
        trace_array_printk_buf(tr->max_buffer.buffer, _THIS_IP_,
            "Failed to swap buffers due to commit in progress\n");
    }

    WARN_ON_ONCE(ret && ret != -EAGAIN && ret != -EBUSY);

    /* (3.4.2) 已经把包含最大latency过程的trace信息备份到max_buffer中了, 
        但是这个信息中包含了多次irqoff的trace信息,哪一次才是最大latency的信息呢?
        使用max_buf->time_start来记录最大latency的起始时间
        max_buf->time_start = data->preempt_timestamp;
     */
    __update_max_tr(tr, tsk, cpu);
    arch_spin_unlock(&tr->max_lock);
}

3.1.2、irqoff trace信息的读取

参考上一节,最大latency的trace信息被备份到了max_buffer中了,且max_buf->time_start来记录最大latency的起始时间。

所以在trace读取irqoff trace信息时,需要从max_buffer读取。tracing_open() -> __tracing_open():

static struct trace_iterator *
__tracing_open(struct inode *inode, struct file *file, bool snapshot)
{

#ifdef CONFIG_TRACER_MAX_TRACE
    /* Currently only the top directory has a snapshot */
    /* (1) 如果tracer的print_max被定义,从max_buffer从读取数据 */
    if (tr->current_trace->print_max || snapshot)
        iter->trace_buffer = &tr->max_buffer;
    else
#endif
        iter->trace_buffer = &tr->trace_buffer;

}

static struct tracer irqsoff_tracer __read_mostly =
{
    .name       = "irqsoff",
    .init       = irqsoff_tracer_init,
    .reset      = irqsoff_tracer_reset,
    .start      = irqsoff_tracer_start,
    .stop       = irqsoff_tracer_stop,
    .print_max  = true,     /* irqoff tracer的print_max为true */
    .print_header   = irqsoff_print_header,
    .print_line     = irqsoff_print_line,
    .flag_changed   = irqsoff_flag_changed,
#ifdef CONFIG_FTRACE_SELFTEST
    .selftest    = trace_selftest_startup_irqsoff,
#endif
    .open           = irqsoff_trace_open,
    .close          = irqsoff_trace_close,
    .allow_instances = true,
    .use_max_tr = true,
};

在读取时,还需要做时间同步只读取max_buf->time_start时间戳以后的数据。tracing_open() -> __tracing_open() -> tracing_iter_reset():

void tracing_iter_reset(struct trace_iterator *iter, int cpu)
{
    struct ring_buffer_event *event;
    struct ring_buffer_iter *buf_iter;
    unsigned long entries = 0;
    u64 ts;

    per_cpu_ptr(iter->trace_buffer->data, cpu)->skipped_entries = 0;

    buf_iter = trace_buffer_iter(iter, cpu);
    if (!buf_iter)
        return;

    ring_buffer_iter_reset(buf_iter);

    /*
     * We could have the case with the max latency tracers
     * that a reset never took place on a cpu. This is evident
     * by the timestamp being before the start of the buffer.
     */
    /* (1) 对于时间戳小于time_start的trace数据全部丢弃掉, 
        只读取time_start时间戳以后的数据
     */
    while ((event = ring_buffer_iter_peek(buf_iter, &ts))) {
        if (ts >= iter->trace_buffer->time_start)
            break;
        entries++;
        ring_buffer_read(buf_iter, NULL);
    }

    per_cpu_ptr(iter->trace_buffer->data, cpu)->skipped_entries = entries;
}

3.1.3、irqsoff tracer的使能

  • irqsoff tracer需要使用function trace或者function_graph trace来记录最大latency的中间函数调用过程。所以它会注册一个global_ops->func,或者使用graph_ops注册。
  • trace数据的写入受trace_hardirqs_off()、trace_hardirqs_on()中配置的开关per_cpu(tracing_cpu, cpu)控制。在trace_hardirqs_off()时打开开关,在trace_hardirqs_on()时关闭开关,这样就记录下了irqoff过程中的具体函数调用过程。

参考“1.2.5、function tracer使能”这一节,tracer的使能最后调用的是tracer->init()函数。

对irqsoff tracer来说,调用的是irqsoff_tracer_init():

static struct tracer irqsoff_tracer __read_mostly =
{
    .name       = "irqsoff",
    .init       = irqsoff_tracer_init,
    .reset      = irqsoff_tracer_reset,
    .start      = irqsoff_tracer_start,
    .stop       = irqsoff_tracer_stop,
    .print_max  = true,
    .print_header   = irqsoff_print_header,
    .print_line     = irqsoff_print_line,
    .flag_changed   = irqsoff_flag_changed,
#ifdef CONFIG_FTRACE_SELFTEST
    .selftest    = trace_selftest_startup_irqsoff,
#endif
    .open           = irqsoff_trace_open,
    .close          = irqsoff_trace_close,
    .allow_instances = true,
    .use_max_tr = true,
};

↓

static int irqsoff_tracer_init(struct trace_array *tr)
{
    trace_type = TRACER_IRQS_OFF;

    return __irqsoff_tracer_init(tr);
}

↓

static int __irqsoff_tracer_init(struct trace_array *tr)
{
    if (irqsoff_busy)
        return -EBUSY;

    save_flags = tr->trace_flags;

    /* non overwrite screws up the latency tracers */
    set_tracer_flag(tr, TRACE_ITER_OVERWRITE, 1);
    set_tracer_flag(tr, TRACE_ITER_LATENCY_FMT, 1);

    tr->max_latency = 0;
    irqsoff_trace = tr;
    /* make sure that the tracer is visible */
    smp_wmb();
    tracing_reset_online_cpus(&tr->trace_buffer);

    /* (1) 如果采用function trace模式来记录最大latency过程中的trace信息 
        global_ops->func = irqsoff_tracer_call()
     */
    ftrace_init_array_ops(tr, irqsoff_tracer_call);

    /* Only toplevel instance supports graph tracing */
    /* (2) 选择使用 function trace模式 or function_graph trace模式 注册
        默认是function trace模式
     */
    if (start_irqsoff_tracer(tr, (tr->flags & TRACE_ARRAY_FL_GLOBAL &&
                      is_graph(tr))))
        printk(KERN_ERR "failed to start irqsoff tracer\n");

    irqsoff_busy = true;
    return 0;
}

↓

start_irqsoff_tracer() -> register_irqsoff_function()

static int register_irqsoff_function(struct trace_array *tr, int graph, int set)
{
    int ret;

    /* 'set' is set if TRACE_ITER_FUNCTION is about to be set */
    if (function_enabled || (!set && !(tr->trace_flags & TRACE_ITER_FUNCTION)))
        return 0;

    if (graph)
        /* (2.1) function_graph trace模式注册 */
        ret = register_ftrace_graph(&irqsoff_graph_return,
                        &irqsoff_graph_entry);
    else
        /* (2.2) function trace模式注册 */
        ret = register_ftrace_function(tr->ops);

    if (!ret)
        function_enabled = true;

    return ret;
}

在记录trace数据时,需要判断per_cpu(tracing_cpu, cpu)开关。以function trace模式为例,我们来看看其irqsoff_tracer_call()函数:

static void
irqsoff_tracer_call(unsigned long ip, unsigned long parent_ip,
            struct ftrace_ops *op, struct pt_regs *pt_regs)
{
    struct trace_array *tr = irqsoff_trace;
    struct trace_array_cpu *data;
    unsigned long flags;

    if (!func_prolog_dec(tr, &data, &flags))
        return;

    trace_function(tr, ip, parent_ip, flags, preempt_count());

    atomic_dec(&data->disabled);
}

↓

static int func_prolog_dec(struct trace_array *tr,
               struct trace_array_cpu **data,
               unsigned long *flags)
{
    long disabled;
    int cpu;

    /*
     * Does not matter if we preempt. We test the flags
     * afterward, to see if irqs are disabled or not.
     * If we preempt and get a false positive, the flags
     * test will fail.
     */
    cpu = raw_smp_processor_id();
    /* 对tracing_cpu开关的判断,由trace_hardirqs_off()、trace_hardirqs_on()函数控制 */
    if (likely(!per_cpu(tracing_cpu, cpu)))
        return 0;

    local_save_flags(*flags);
    /*
     * Slight chance to get a false positive on tracing_cpu,
     * although I'm starting to think there isn't a chance.
     * Leave this for now just to be paranoid.
     */
    if (!irqs_disabled_flags(*flags) && !preempt_count())
        return 0;

    *data = per_cpu_ptr(tr->trace_buffer.data, cpu);
    disabled = atomic_inc_return(&(*data)->disabled);

    if (likely(disabled == 1))
        return 1;

    atomic_dec(&(*data)->disabled);

    return 0;
}

3.2、数据存入

它的trace会提供几部分信息:

  • 1、irqoff的最大时长:latency;
  • 2、在最大irqoff这期间所有的function trace信息;
  • 3、最后的irqon的函数回调信息;

具体的存入方法参考“3.1、插桩”这一节的描述。

3.3、数据读出

从trace文件读出的irqoff tracer默认数据格式为:(显示最大latency以及过程中的详细函数调用)

# cat trace
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 4.4.21-g6678ed0-01979-g8d8eeb5-cIcd1f2d8-dirty
# --------------------------------------------------------------------
# latency: 1703 us, #184/184, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:8)
#    -----------------
#    | task: swapper/3-0 (uid:0 nice:0 policy:0 rt_prio:0)
#    -----------------
#  => started at: lpm_cpuidle_enter
#  => ended at:   cpuidle_enter_state
#
#
#                  _------=> CPU#
#                 / _-----=> irqs-off
#                | / _----=> need-resched
#                || / _---=> hardirq/softirq
#                ||| / _--=> preempt-depth
#                |||| /     delay
#  cmd     pid   ||||| time  |   caller
#     \   /      |||||  \    |   /
  <idle>-0       3dn.1    3us : psci_enter_sleep <-lpm_cpuidle_enter
  <idle>-0       3dn.2    9us : update_debug_pc_event <-psci_enter_sleep
  <idle>-0       3dn.2   11us : _raw_spin_lock <-update_debug_pc_event
  <idle>-0       3dn.2   13us : preempt_count_add <-_raw_spin_lock
  <idle>-0       3dn.3   17us : do_raw_spin_trylock <-_raw_spin_lock
  <idle>-0       3dn.3   20us : _raw_spin_unlock <-update_debug_pc_event
  <idle>-0       3dn.3   23us : do_raw_spin_unlock <-_raw_spin_unlock
  <idle>-0       3dn.3   25us : preempt_count_sub <-_raw_spin_unlock
  <idle>-0       3dn.2   28us : ktime_get <-lpm_cpuidle_enter
  <idle>-0       3dn.2   33us : arch_counter_read <-ktime_get
  <idle>-0       3dn.2   35us : lpm_stats_cpu_exit <-lpm_cpuidle_enter
  <idle>-0       3dn.2   37us : update_level_stats.part.1 <-lpm_stats_cpu_exit
  <idle>-0       3dn.2   41us : cluster_unprepare <-lpm_cpuidle_enter
  <idle>-0       3dn.2   43us : _raw_spin_lock <-cluster_unprepare
  <idle>-0       3dn.2   44us : preempt_count_add <-_raw_spin_lock
  <idle>-0       3dn.3   46us : do_raw_spin_trylock <-_raw_spin_lock
  <idle>-0       3dn.3   50us : lpm_stats_cluster_exit <-cluster_unprepare
  <idle>-0       3dn.3   52us : update_level_stats.part.1 <-lpm_stats_cluster_exit
  <idle>-0       3dn.3   57us : update_debug_pc_event <-cluster_unprepare
  <idle>-0       3dn.3   59us : _raw_spin_lock <-update_debug_pc_event
  <idle>-0       3dn.3   60us : preempt_count_add <-_raw_spin_lock
  <idle>-0       3dn.4   62us : do_raw_spin_trylock <-_raw_spin_lock
  <idle>-0       3dn.4   65us : _raw_spin_unlock <-update_debug_pc_event
  <idle>-0       3dn.4   66us : do_raw_spin_unlock <-_raw_spin_unlock
  <idle>-0       3dn.4   69us : preempt_count_sub <-_raw_spin_unlock
  <idle>-0       3dn.3   71us : sched_set_cluster_dstate <-cluster_unprepare
  <idle>-0       3dn.3   74us : cpu_cluster_pm_exit <-cluster_unprepare
  <idle>-0       3dn.3   76us : _raw_read_lock <-cpu_cluster_pm_exit
  <idle>-0       3dn.3   78us : preempt_count_add <-_raw_read_lock
  <idle>-0       3dn.4   80us : do_raw_read_trylock <-_raw_read_lock
  <idle>-0       3dn.4   82us : cpu_pm_notify <-cpu_cluster_pm_exit
  <idle>-0       3dn.4   84us : __raw_notifier_call_chain <-cpu_pm_notify
  <idle>-0       3dn.4   86us : notifier_call_chain <-__raw_notifier_call_chain
  <idle>-0       3dn.4   88us : gic_cpu_pm_notifier <-notifier_call_chain
  <idle>-0       3dn.4   90us : arch_timer_cpu_pm_notify <-notifier_call_chain
  <idle>-0       3dn.4   92us : jtag_cpu_pm_callback <-notifier_call_chain
  <idle>-0       3dn.4   95us : gladiator_erp_pm_callback <-notifier_call_chain
  <idle>-0       3dn.4   97us : cpu_pm_pmu_notify <-notifier_call_chain
  <idle>-0       3dn.4   99us : cpu_pm_pmu_common <-cpu_pm_pmu_notify
  <idle>-0       3dn.4  102us : cti_cpu_pm_callback <-notifier_call_chain
  <idle>-0       3dn.4  104us : coresight_cti_ctx_restore <-cti_cpu_pm_callback
  <idle>-0       3dn.4  109us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
  <idle>-0       3dn.4  111us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  113us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  222us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
  <idle>-0       3dn.5  224us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.5  226us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4  228us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
  <idle>-0       3dn.4  230us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  232us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  337us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
  <idle>-0       3dn.5  339us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.5  341us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4  343us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
  <idle>-0       3dn.4  346us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  348us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  492us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
  <idle>-0       3dn.5  494us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.5  497us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4  499us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
  <idle>-0       3dn.4  501us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  503us+: do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.5  603us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
  <idle>-0       3dn.5  605us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.5  607us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4  613us : fpsimd_cpu_pm_notifier <-notifier_call_chain
  <idle>-0       3dn.4  615us : _raw_read_unlock <-cpu_cluster_pm_exit
  <idle>-0       3dn.4  617us : do_raw_read_unlock <-_raw_read_unlock
  <idle>-0       3dn.4  619us : preempt_count_sub <-_raw_read_unlock
  <idle>-0       3dn.3  621us : update_cluster_history <-cluster_unprepare
  <idle>-0       3dn.3  623us : ktime_get <-update_cluster_history
  <idle>-0       3dn.3  626us : arch_counter_read <-ktime_get
  <idle>-0       3dn.3  629us : cluster_unprepare <-cluster_unprepare
  <idle>-0       3dn.3  631us : _raw_spin_lock <-cluster_unprepare
  <idle>-0       3dn.3  633us : preempt_count_add <-_raw_spin_lock
  <idle>-0       3dn.4  635us : do_raw_spin_trylock <-_raw_spin_lock
  <idle>-0       3dn.4  638us : _raw_spin_unlock <-cluster_unprepare
  <idle>-0       3dn.4  640us : do_raw_spin_unlock <-_raw_spin_unlock
  <idle>-0       3dn.4  642us : preempt_count_sub <-_raw_spin_unlock
  <idle>-0       3dn.3  644us : _raw_spin_unlock <-cluster_unprepare
  <idle>-0       3dn.3  645us : do_raw_spin_unlock <-_raw_spin_unlock
  <idle>-0       3dn.3  647us : preempt_count_sub <-_raw_spin_unlock
  <idle>-0       3dn.2  649us : tick_broadcast_oneshot_control <-lpm_cpuidle_enter
  <idle>-0       3dn.2  651us : __tick_broadcast_oneshot_control <-tick_broadcast_oneshot_control
  <idle>-0       3dn.2  653us : _raw_spin_lock <-__tick_broadcast_oneshot_control
  <idle>-0       3dn.2  654us : preempt_count_add <-_raw_spin_lock
  <idle>-0       3dn.3  657us : do_raw_spin_trylock <-_raw_spin_lock
  <idle>-0       3dn.3  660us : clockevents_switch_state <-__tick_broadcast_oneshot_control
  <idle>-0       3dn.3  663us : ktime_get <-__tick_broadcast_oneshot_control
  <idle>-0       3dn.3  667us : arch_counter_read <-ktime_get
  <idle>-0       3dn.3  668us : tick_program_event <-__tick_broadcast_oneshot_control
  <idle>-0       3dn.3  670us : clockevents_program_event <-tick_program_event
  <idle>-0       3dn.3  672us : ktime_get <-clockevents_program_event
  <idle>-0       3dn.3  676us : arch_counter_read <-ktime_get
  <idle>-0       3dn.3  678us : arch_timer_set_next_event_virt <-clockevents_program_event
  <idle>-0       3dn.3  679us : _raw_spin_unlock <-__tick_broadcast_oneshot_control
  <idle>-0       3dn.3  681us : do_raw_spin_unlock <-_raw_spin_unlock
  <idle>-0       3dn.3  683us : preempt_count_sub <-_raw_spin_unlock
  <idle>-0       3dn.2  685us : cpu_pm_exit <-lpm_cpuidle_enter
  <idle>-0       3dn.2  686us : _raw_read_lock <-cpu_pm_exit
  <idle>-0       3dn.2  688us : preempt_count_add <-_raw_read_lock
  <idle>-0       3dn.3  689us : do_raw_read_trylock <-_raw_read_lock
  <idle>-0       3dn.3  691us : cpu_pm_notify <-cpu_pm_exit
  <idle>-0       3dn.3  693us : __raw_notifier_call_chain <-cpu_pm_notify
  <idle>-0       3dn.3  694us : notifier_call_chain <-__raw_notifier_call_chain
  <idle>-0       3dn.3  696us : gic_cpu_pm_notifier <-notifier_call_chain
  <idle>-0       3dn.3  698us : gic_cpu_sys_reg_init <-gic_cpu_pm_notifier
  <idle>-0       3dn.3  700us : arch_timer_cpu_pm_notify <-notifier_call_chain
  <idle>-0       3dn.3  702us : jtag_cpu_pm_callback <-notifier_call_chain
  <idle>-0       3dn.3  704us : msm_jtag_restore_state <-jtag_cpu_pm_callback
  <idle>-0       3dn.3  708us : msm_jtag_etm_restore_state <-msm_jtag_restore_state
  <idle>-0       3dn.3  716us : raw_notifier_call_chain <-msm_jtag_etm_restore_state
  <idle>-0       3dn.3  717us!: notifier_call_chain <-raw_notifier_call_chain
  <idle>-0       3dn.3 1524us : gladiator_erp_pm_callback <-notifier_call_chain
  <idle>-0       3dn.3 1526us : cpu_pm_pmu_notify <-notifier_call_chain
  <idle>-0       3dn.3 1529us : armv8pmu_reset <-cpu_pm_pmu_notify
  <idle>-0       3dn.3 1531us : cpu_pm_pmu_common <-cpu_pm_pmu_notify
  <idle>-0       3dn.3 1533us : cpu_pm_pmu_setup <-cpu_pm_pmu_common
  <idle>-0       3dn.3 1538us : rcu_irq_enter <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1542us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
  <idle>-0       3dn.3 1545us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
  <idle>-0       3dn.3 1553us : armpmu_start <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1558us : armpmu_event_set_period <-armpmu_start
  <idle>-0       3dn.3 1562us : __rcu_read_lock <-perf_event_update_userpage
  <idle>-0       3dn.3 1566us : __rcu_read_unlock <-perf_event_update_userpage
  <idle>-0       3dn.3 1568us : armv8pmu_enable_event <-armpmu_start
  <idle>-0       3dn.3 1570us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
  <idle>-0       3dn.3 1572us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1574us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1576us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
  <idle>-0       3dn.4 1578us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4 1581us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.3 1584us : rcu_irq_exit <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1586us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
  <idle>-0       3dn.3 1591us : rcu_irq_enter <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1593us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
  <idle>-0       3dn.3 1596us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
  <idle>-0       3dn.3 1598us : armpmu_start <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1601us : armpmu_event_set_period <-armpmu_start
  <idle>-0       3dn.3 1603us : __rcu_read_lock <-perf_event_update_userpage
  <idle>-0       3dn.3 1605us : __rcu_read_unlock <-perf_event_update_userpage
  <idle>-0       3dn.3 1606us : armv8pmu_enable_event <-armpmu_start
  <idle>-0       3dn.3 1608us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
  <idle>-0       3dn.3 1611us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1615us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1617us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
  <idle>-0       3dn.4 1619us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4 1622us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.3 1623us : rcu_irq_exit <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1627us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
  <idle>-0       3dn.3 1629us : rcu_irq_enter <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1632us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
  <idle>-0       3dn.3 1634us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
  <idle>-0       3dn.3 1637us : armpmu_start <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1639us : armpmu_event_set_period <-armpmu_start
  <idle>-0       3dn.3 1642us : __rcu_read_lock <-perf_event_update_userpage
  <idle>-0       3dn.3 1644us : __rcu_read_unlock <-perf_event_update_userpage
  <idle>-0       3dn.3 1646us : armv8pmu_enable_event <-armpmu_start
  <idle>-0       3dn.3 1647us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
  <idle>-0       3dn.3 1649us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1651us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1653us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
  <idle>-0       3dn.4 1655us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4 1657us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.3 1659us : rcu_irq_exit <-cpu_pm_pmu_setup
  <idle>-0       3dn.3 1662us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
  <idle>-0       3dn.3 1665us : armv8pmu_start <-cpu_pm_pmu_common
  <idle>-0       3dn.3 1667us : _raw_spin_lock_irqsave <-armv8pmu_start
  <idle>-0       3dn.3 1670us : preempt_count_add <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1672us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
  <idle>-0       3dn.4 1674us : _raw_spin_unlock_irqrestore <-armv8pmu_start
  <idle>-0       3dn.4 1676us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.4 1679us : preempt_count_sub <-_raw_spin_unlock_irqrestore
  <idle>-0       3dn.3 1680us : cti_cpu_pm_callback <-notifier_call_chain
  <idle>-0       3dn.3 1683us : fpsimd_cpu_pm_notifier <-notifier_call_chain
  <idle>-0       3dn.3 1684us : _raw_read_unlock <-cpu_pm_exit
  <idle>-0       3dn.3 1686us : do_raw_read_unlock <-_raw_read_unlock
  <idle>-0       3dn.3 1688us : preempt_count_sub <-_raw_read_unlock
  <idle>-0       3dn.2 1691us : sched_set_cpu_cstate <-lpm_cpuidle_enter
  <idle>-0       3dn.2 1693us : ktime_get <-lpm_cpuidle_enter
  <idle>-0       3dn.2 1697us : arch_counter_read <-ktime_get
  <idle>-0       3dn.1 1702us : lpm_cpuidle_enter <-cpuidle_enter_state
  <idle>-0       3dn.1 1708us+: trace_hardirqs_on <-cpuidle_enter_state
  <idle>-0       3dn.1 1720us : <stack trace>
 => trace_hardirqs_on
 => lpm_cpuidle_enter
 => cpuidle_enter_state
 => cpuidle_enter
 => cpu_startup_entry
 => secondary_start_kernel
 =>

在通过trace文件读出数据时:

  • 具体函数调用信息是使用trace_function()或者trace_graph_function()函数进行记录的,调用相应的方法解析即可;
  • 最后的irqon的函数回调信息是使用__trace_stack()函数进行记录的,调用相应的方法解析即可;
  • 最大latency信息的解析,使用tracer->print_header();

在第一次读操作时,会打印header。seq_read() -> s_show() -> print_trace_line() -> tracer->print_header():

static void irqsoff_print_header(struct seq_file *s)
{
    trace_default_header(s);
}

参考资料:

1、Function Tracer Design
2、ftrace function_graph分析

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值