Skip to the content.

fprobe 机制

BPF syscall 处理逻辑 (kernel/bpf/syscall.c)

  case BPF_PROG_TYPE_KPROBE:
      if (attr->link_create.attach_type == BPF_PERF_EVENT)
          ret = bpf_perf_link_attach(attr, prog);           // ← 普通 kprobe
      else if (attr->link_create.attach_type == BPF_TRACE_KPROBE_MULTI ||
               attr->link_create.attach_type == BPF_TRACE_KPROBE_SESSION)
          ret = bpf_kprobe_multi_link_attach(attr, prog);   // ← fprobe

用户必须显式选择: - BPF_PERF_EVENT → 使用传统 kprobe(通过 perf_event 子系统) - BPF_TRACE_KPROBE_MULTI → 使用 fprobe

关键文档 Documentation/trace/fprobetrace.rst

从 bpftrace 到 kprobe 的记录

没想到把,调用的路线居然是: bpf -> kernel/events -> kernel/trace -> arch/x86/kernel/kprobes

#0  arch_remove_kprobe (p=0xffff8880144f3c18) at arch/x86/kernel/kprobes/core.c:834
#1  __unregister_trace_kprobe (tk=tk@entry=0xffff8880144f3c00) at kernel/trace/trace_kprobe.c:529
#2  destroy_local_trace_kprobe (event_call=<optimized out>) at kernel/trace/trace_kprobe.c:1958
#3  _free_event (event=event@entry=0xffff888008478530) at kernel/events/core.c:5357
#4  put_event (event=0xffff888008478530) at kernel/events/core.c:5454
#5  perf_event_release_kernel (event=0xffff888008478530) at kernel/events/core.c:5579
#6  0xffffffff812d54d2 in perf_release (inode=<optimized out>, file=<optimized out>) at kernel/events/core.c:5589
#7  0xffffffff81405e4c in __fput (file=0xffff88800e3a46c0) at fs/file_table.c:431
#8  0xffffffff814060c9 in __fput_sync (file=<optimized out>) at fs/file_table.c:516
#9  0xffffffff814003fc in __do_sys_close (fd=<optimized out>) at fs/open.c:1567
#10 __se_sys_close (fd=<optimized out>) at fs/open.c:1552
#11 __x64_sys_close (regs=<optimized out>) at fs/open.c:1552
#12 0xffffffff821068fc in do_syscall_x64 (nr=3, regs=0xffffc90000b7ff58) at arch/x86/entry/common.c:52
#13 do_syscall_64 (regs=0xffffc90000b7ff58, nr=3) at arch/x86/entry/common.c:83
#14 entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:121

源码分析

ftrace_ops 是一个通用的机制,例如:

static void fprobe_init(struct fprobe *fp)
{
	fp->nmissed = 0;
	if (fprobe_shared_with_kprobes(fp))
		fp->ops.func = fprobe_kprobe_handler;
	else
		fp->ops.func = fprobe_handler;
	fp->ops.flags |= FTRACE_OPS_FL_SAVE_REGS;
}

trace_fprobe.c 的作用

看上去就是围绕 /sys/kernel/debug/tracing/dynamic_events 来提供操作,其核心为

static struct dyn_event_operations trace_fprobe_ops = {
	.create = trace_fprobe_create,
	.show = trace_fprobe_show,
	.is_busy = trace_fprobe_is_busy,
	.free = trace_fprobe_release,
	.match = trace_fprobe_match,
};

为什么 bpf 使用 fprobe

History:        #0
Commit:         0dcac272540613d41c05e89679e4ddb978b612f1
Author:         Jiri Olsa <jolsa@kernel.org>
Committer:      Alexei Starovoitov <ast@kernel.org>
Author Date:    Wed 16 Mar 2022 08:24:09 PM CST
Committer Date: Fri 18 Mar 2022 11:17:18 AM CST

bpf: Add multi kprobe link

Adding new link type BPF_LINK_TYPE_KPROBE_MULTI that attaches kprobe
program through fprobe API.

The fprobe API allows to attach probe on multiple functions at once
very fast, because it works on top of ftrace. On the other hand this
limits the probe point to the function entry or return.

The kprobe program gets the same pt_regs input ctx as when it's attached
through the perf API.

Adding new attach type BPF_TRACE_KPROBE_MULTI that allows attachment
kprobe to multiple function with new link.

User provides array of addresses or symbols with count to attach the
kprobe program to. The new link_create uapi interface looks like:

  struct {
          __u32           flags;
          __u32           cnt;
          __aligned_u64   syms;
          __aligned_u64   addrs;
  } kprobe_multi;

The flags field allows single BPF_TRACE_KPROBE_MULTI bit to create
return multi kprobe.

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20220316122419.933957-4-jolsa@kernel.org

和想象的很不一样,这个函数调用很容易,甚至是

sudo bpftrace -e 'kprobe:do_sys_openat2 { print("hit kprobe:do_sys_openat2") }'
      - entry_SYSCALL_64
        - do_syscall_64
          - do_syscall_x64
            - __x64_sys_bpf
              - __se_sys_bpf
                - __do_sys_bpf
                  - __sys_bpf
                    - link_create
                      - bpf_kprobe_multi_link_attach
                        - register_fprobe_ips

fprobe 的定位 : ftrace 的封装

从 trace_probe_add_file 的调用位置

第一个 commit 的说明

commit cad9931f64dc7f5dbdec12cae9f30063360f9855
Author: Masami Hiramatsu <orgakes filtering patterns of the functin names.
     - register_fprobe_ips() takes an array of ftrace-location addresses.
     - register_fprobe_syms() takes an array of function names.

    The registered fprobes can be unregistered with unregister_fprobe().
    e.g.

    struct fprobe fp = { .entry_handler = user_handler };
    const char *targets[] = { "func1", "func2", "func3"};
    ...

    ret = register_fprobe_syms(&fp, targets, ARRAY_SIZE(targets));

    ...

    unregister_fprobe(&fp);

    Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
    Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
    Tested-by: Steven Rostedt (Google) <rostedt@goodmis.org>
    Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    Link: https://lore.kernel.org/bpf/164735283857.1084943.1154436951479395551.stgit@devnote2
>
Date:   Tue Mar 15 23:00:38 2022 +0900

    fprobe: Add ftrace based probe APIs

    The fprobe is a wrapper API for ftrace function tracer.
    Unlike kprobes, this probes only supports the function entry, but this
    can probe multiple functions by one fprobe. The usage is similar, user
    will set their callback to fprobe::entry_handler and call
    register_fprobe*() with probed functions.
    There are 3 registration interfaces,

     - register_fprobe() tmhiramat@kernel.

Documentation/trace/fprobe.rst

https://www.kernel.org/doc/html/latest/trace/fprobe.html

Fprobe is a function entry/exit probe mechanism based on ftrace. Instead of using ftrace full feature, if you only want to attach callbacks on function entry and exit, similar to the kprobes and kretprobes, you can use fprobe. Compared with kprobes and kretprobes, fprobe gives faster instrumentation for multiple functions with single handler. This document describes how to use fprobe.

从 samples/fprobe/fprobe_example.c 看,就是用于注册 hook

kprobe 是可以在任何地方打点的

这是 fprobe 的语法:

  f[:[GRP1/][EVENT1]] SYM [FETCHARGS]                       : Probe on function entry

这是 kprobetrace 的语法

  p[:[GRP/][EVENT]] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]	: Set a probe

测试参考 docs/trace/code/ftrace-example.sh 中的 kprobe_in_function_body kprobe_in_function_body

在这里介绍了分别使用 trace-cmd 和 perf 来实现在任何地方打点: https://walac.github.io/kernel-tracing/

但是,知道有这个效果就可以了,还需要使用 vmlinux ,看着就脑袋疼。

需要验证这个东西是什么

那么,也就是说, kprobe 可以通过三个方法获取:

  1. perf_event_open
  2. bpf
  3. 内核模块

[!NOTE] 参考神奇海螺的意见,有待验证

方式1: 通过 perf_event 系统调用

// 用户态代码
struct perf_event_attr attr = {
    .type = PERF_TYPE_PROBE,            // PMU 类型
    .kprobe_func = (u64)"do_nanosleep", // 探测函数
    .probe_offset = 0,
};
syscall(__NR_perf_event_open, &attr, ...);

// 内核: perf_event → perf_kprobe → kprobe

方式2: 直接 kprobe API

// 内核模块代码
struct kprobe kp = {
    .symbol_name = "do_nanosleep",
    .pre_handler = handler,
};
register_kprobe(&kp);

// 内核: 直接调用 kprobe 子系统

方式3: eBPF 中的选择

// eBPF 程序中通过 attach_type 选择底层机制

// 使用传统 kprobe(通过 perf_event)
BPF_PROG_TYPE_KPROBE + BPF_PERF_EVENT
     bpf_perf_link_attach()
     perf_event  kprobe

// 使用 fprobe(批量探测)
BPF_PROG_TYPE_KPROBE + BPF_TRACE_KPROBE_MULTI
     bpf_kprobe_multi_link_attach()
     fprobe  ftrace

BPF attach_type 路径总结

attach_type 路径 适用场景
BPF_PERF_EVENT BPF → perf_event → kprobe 单个函数探测,传统方式
BPF_TRACE_KPROBE_MULTI BPF → fprobe 批量函数探测,性能更好
BPF_TRACE_KPROBE_SESSION BPF → fprobe entry/exit 成对探测

kprobe 和 perf 子系统的关系

也就是这个?

static struct pmu perf_kprobe = {
    .task_ctx_nr  = perf_sw_context,
    .event_init   = perf_kprobe_event_init,    // 初始化 kprobe
    .add          = perf_trace_add,             // 添加事件
    .del          = perf_trace_del,             // 删除事件
    .start        = perf_swevent_start,
    .stop         = perf_swevent_stop,
    .read         = perf_swevent_read,
    .attr_groups  = kprobe_attr_groups,
};

那么为什么没有定义一个 fprobe 类似的结构?

真的是这个结构吗? 似乎是的,但是细节需要确认

┌─────────────────────────────────────────────────────────────────────┐
│                        用户空间                                      │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐                      │
│  │ perf_event()│ │  bpf()   │    │ftrace cmd│                      │
│  └────┬─────┘    └────┬─────┘    └────┬─────┘                      │
└───────┼───────────────┼───────────────┼──────────────────────────────┘
        │               │               │
        ▼               ▼               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        内核层                                        │
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                    perf_event 子系统                          │   │
│  │  ┌──────────────────────────────────────────────────────┐   │   │
│  │  │  PMU (Performance Monitoring Unit) 抽象层            │   │   │
│  │  │                                                      │   │   │
│  │  │  ┌────────────┐  ┌────────────┐  ┌────────────┐     │   │   │
│  │  │  │ perf_hw    │  │ perf_sw    │  │ perf_kprobe│     │   │   │
│  │  │  │ (硬件PMU)  │  │ (软件事件)  │  │ (kprobe)   │     │   │   │
│  │  │  └────────────┘  └────────────┘  └─────┬──────┘     │   │   │
│  │  └─────────────────────────────────────────┼────────────┘   │   │
│  └────────────────────────────────────────────┼─────────────────┘   │
│                                               │                      │
│                                               ▼                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │         - kprobe 子系统 (kernel/kprobes.c)                 │   │
│  │         - 注册/注销 kprobe                                     │   │
│  │         - 断点处理 (int3/ebreak)                              │   │
│  │         - 调用用户回调                                         │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │         - fprobe 子系统 (kernel/trace/fprobe.c)            │   │
│  │         - 基于 ftrace 的批量函数探测                           │   │
│  │         - entry/exit 统一回调                                  │   │
│  │         - 通过 function-graph 实现返回探测                     │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

fprobe 是完全绕过 kprobe 的

fprobe 不直接使用 kernel/kprobes.c,它是基于 fgraph(function graph tracer) 和 ftrace 实现的。

但 fprobe 有一个可选功能可以与 kprobes 共享回调:

// include/linux/fprobe.h #define FPROBE_FL_KPROBE_SHARED 2 // 与 kprobes 共享 handler

static inline bool fprobe_shared_with_kprobes(struct fprobe *fp) { return (fp) ? fp->flags & FPROBE_FL_KPROBE_SHARED : false; }

fprobe 的真实实现层次

┌─────────────────────────────────────────────────────────────────┐ │ fprobe 用户层 (kernel/trace/trace_fprobe.c) │ │ • 通过 tracefs 暴露 fprobe_events 接口 │ │ • 类似 trace_kprobe.c,但基于 fprobe 而非 kprobes │ ├─────────────────────────────────────────────────────────────────┤ │ fprobe 核心层 (kernel/trace/fprobe.c) │ │ • 基于 fgraph_ops 和 ftrace_ops 实现 │ │ • 【不依赖 kprobes.c】 │ ├─────────────────────────────────────────────────────────────────┤ │ fgraph/ftrace 层 (kernel/trace/fgraph.c, kernel/trace/ftrace.c) │ │ • 函数图跟踪基础设施 │ │ • 使用 ftrace 的动态代码修改能力 │ └─────────────────────────────────────────────────────────────────┘

fprobe 与 kprobes 的关系

特性 fprobe kprobe ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 基础机制 fgraph + ftrace 断点指令 (break) 单步执行 无需(利用函数返回) 需要(指令模拟/单步) 性能 更高(无断点开销) 较低(断点 + 单步) 适用场景 函数入口/出口探测 任意指令位置探测

fprobe 使用 fgraph 的证据

// kernel/trace/fprobe.c static struct fgraph_ops fprobe_graph_ops = { .entryfunc = fprobe_fgraph_entry, // 函数入口回调 .exitfunc = fprobe_return, // 函数返回回调 };

// 注册时使用 fgraph 接口 fgraph_get_fgraph_ops(&fprobe_graph_ops); // 获取 fgraph 操作符

为什么有 kprobes 相关代码?

fprobe 代码中的 kprobes 引用是为了防止与 kprobes 冲突:

// kernel/trace/fprobe.c static inline int __fprobe_kprobe_handler(…) { // 如果已经有 kprobe 在运行,跳过避免递归 if (unlikely(kprobe_running())) { fp->nmissed++; return 0; }

kprobe_busy_begin();
ret = __fprobe_handler(ip, parent_ip, fp, fregs, data);
kprobe_busy_end();
return ret; }

这只是互斥标记,不是功能依赖。

对比:trace_kprobe vs trace_fprobe

层次 trace_kprobe trace_fprobe ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 上层 trace_kprobe.c trace_fprobe.c 公共代码 trace_probe.c trace_probe.c 核心机制 kernel/kprobes.c kernel/trace/fprobe.c 底层基础 arch/*/kernel/kprobes.c kernel/trace/fgraph.c + kernel/trace/ftrace.c

所以 fprobe 完全绕过了 kprobes 子系统,是一个独立的轻量级探测机制。

fprobetrace.rst 中介绍了 fprobe 可以使用 BTF 的,那么 kprobetrace 也可以吗?

还是 kprobetrace 只是可以在函数入口可以的?

还是

无论如何,fprobe 是通过 fgraph 实现,都是很奇怪的

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。