跳转至

第1篇:ftrace 核心 — mcount 桩、ftrace_ops 与动态替换

源码:kernel/trace/ftrace.c | 头文件:include/linux/ftrace.h

系列目录:ftrace 内核源码深度解析


1. ftrace 的基本原理

ftrace 的核心机制是在编译时给每个函数入口插入 mcount 桩

// 编译后的形态
void foo(void) {
    __fentry__();  // ← GCC -pg / -mfentry 插入的桩
    // 函数体
}

内核将所有函数的 mcount 桩替换为 NOP(无跟踪时)或调用跟踪器的函数(跟踪时)。不跟踪时完全零开销,跟踪时动态替换为实际函数。


2. struct ftrace_ops — 跟踪器注册的实体

// include/linux/ftrace.h:447
struct ftrace_ops {
    ftrace_func_t       func;           // ★ 跟踪回调函数
    ftrace_func_t       saved_func;     // 上一个跟踪器的回调(链式切换)
    struct ftrace_ops   *next;          // 链式 ops 链表(全局链表)
    unsigned long       flags;          // FTRACE_OPS_FL_* 标志
    int __percpu        *disabled;      // per-CPU 禁用的计数器
    struct ftrace_ops   *local_ops;     // 每 CPU 本地 ops
    void                *private;       // 跟踪器私有数据
    struct ftrace_hash  *func_hash;     // ★ set_ftrace_filter 过滤器
    struct ftrace_hash  *notrace_hash;  // set_ftrace_notrace 反向过滤器

#ifdef CONFIG_DYNAMIC_FTRACE
    ftrace_func_t       trampoline;     // 跳板函数(动态 ftrace)
    int                 trampoline_size;
    unsigned long       trampoline_status;
#endif
};

三个核心字段: - func:实际被 mcount 桩调用的回调函数——每个被跟踪的函数调用都会经过这个回调 - func_hashset_ftrace_filter 设置的函数白名单(只跟踪这些函数) - notrace_hashset_ftrace_notrace 设置的反向黑名单(永远不跟踪这些函数)


3. register_ftrace_function — 注册跟踪器

// kernel/trace/ftrace.c
int register_ftrace_function(struct ftrace_ops *ops)
{
    // ① 验证: ops->func 不能为 NULL
    // ② 加入全局 ftrace_ops_list 链表
    //    ftrace_ops_list → ops1 → ops2 → ... → ftrace_list_end
    // ③ 调用 ftrace_startup
    //    → 对所有 mcount 桩: NOP → call ftrace_caller
    //    → ftrace_caller 遍历 ftrace_ops_list,调用每个 ops->func
    // ④ 返回 0
}

void unregister_ftrace_function(struct ftrace_ops *ops)
{
    // ① 从 ftrace_ops_list 移除
    // ② 调用 ftrace_shutdown
    //    → 如果链表为空: ftrace_caller → NOP(恢复零开销)
}

关键:每次注册/注销都导致所有函数的 mcount 桩被重写。但因为有缓存(record 列表),只需更新那些真正被过滤到的函数。


4. 桩的类型 — NOP、ftrace_caller、REG

ftrace 维护每个函数的"记录"(struct dyn_ftrace):

// include/linux/ftrace.h
struct dyn_ftrace {
    unsigned long ip;               // 函数入口的 IP 地址
    unsigned long flags;            // FTRACE_FL_* 标志
    unsigned long counter;          // 此函数被多少个 ops 跟踪
    struct dyn_arch_ftrace arch;    // 架构特定的 NOP/REG 信息
};

三种桩状态

状态 mcount 处实际代码 何时使用
NOP nop (x86: 5-byte NOP) 函数不被任何跟踪器跟踪
CALL call ftrace_caller 函数被至少一个跟踪器跟踪
REGS call ftrace_regs_caller 跟踪器需要寄存器快照 (SAVE_REGS flag)

内核只在状态转换时修改代码:

NOP → CALL (register_ftrace_function):
  ftrace_make_call(rec, ip) → 修改 mcount 处的代码,从 nop 改为 call ftrace_caller

CALL → NOP (unregister_ftrace_function):
  ftrace_make_nop(rec, ip) → 修改 mcount 处的代码,从 call 改为 nop

5. 函数过滤 — set_ftrace_filter

// 通过 /sys/kernel/tracing/set_ftrace_filter 控制
// echo "do_sys_open" > set_ftrace_filter
// echo "tcp_*" > set_ftrace_filter
// echo ":mod:ext4" > set_ftrace_filter

// 内核侧: ftrace_ops->func_hash 存储过滤列表
// update_ops_hash(ops, hash) → 重新扫描 mcount 桩
// 只对匹配的函数: NOP → CALL

匹配链

ftrace_match_records(hash, func_name)
  → 遍历 dyn_ftrace 记录表
  → glob_match(rec->ip → kallsyms_lookup → function_name, filter)
  → 匹配的函数标记为 ENABLED
  → ftrace_update_code → 修改 mcount 桩


6. 动态 ftrace(DYNAMIC_FTRACE)

动态 ftrace 允许运行时添加/删除跟踪点,不需要重新编译内核。实现步骤:

启动时 (ftrace_init):
  ① 扫描所有编译时记录的 mcount 调用点 (__mcount_loc 段)
  ② 创建 struct dyn_ftrace 记录数组
  ③ 将所有 mcount 桩改为 NOP

注册时:
  ① 更新 func_hash 过滤表
  ② ftrace_update_code → 匹配的函数桩: NOP → CALL
  ③ 通过 __stop_machine 原子操作保证所有 CPU 的一致性

__stop_machine 是关键:重写 mcount 桩需要所有 CPU 都停止(类似 FTEXT 段修改),否则可能出现不一致的代码读取。


7. mcount 桩的具体实现 (x86)

// arch/x86/kernel/ftrace_64.S
ENTRY(ftrace_caller)
    pushq %rbp                        ; 保存调用方栈帧
    movq %rsp, %rbp

    /* 计算父亲函数的 IP (mcount 被调用时的返回地址) */
    movq 8(%rbp), %rax               ; parent_ip = 返回地址
    subq $MCOUNT_INSN_SIZE, %rax     ; 修正为 mcount 指令本身

    /* 遍历 ftrace_ops_list,调用每个 ops->func */
    call ftrace_ops_list_func

    popq %rbp                         ; 恢复栈帧
    ret

8. 总结

组件 关键函数/结构 核心作用
mcount 桩 __fentry__ 编译时自动插入到每个函数入口
dyn_ftrace 记录 dyn_ftrace.ip / .flags 每个函数的跟踪状态
桩类型 NOP / CALL / REGS 零开销 / 有跟踪器 / 有寄存器要求
filter func_hash / notrace_hash set_ftrace_filter/set_ftrace_notrace
注册/注销 register_ftrace_function / unregister_ftrace_function 加入/移除 ftrace_ops_list
代码修改 ftrace_make_call / ftrace_make_nop NOP ↔ CALL 桩切换

下一篇文章

第2篇:Tracer 引擎 — trace_array、function/function_graph 与 trace_pipe


💬 评论