Fprobe - 函数进入/退出探针

简介

Fprobe 是一种基于 ftrace 的函数进入/退出探针机制。如果您只想在函数进入和退出时附加回调,类似于 kprobes 和 kretprobes,而不是使用 ftrace 的完整功能,则可以使用 fprobe。与 kprobes 和 kretprobes 相比,fprobe 可以使用单个处理程序更快地检测多个函数。本文档描述了如何使用 fprobe。

fprobe 的用法

fprobe 是 ftrace(+ 类似 kretprobe 的返回回调)的包装器,用于将回调附加到多个函数的进入和退出。用户需要设置 struct fprobe 并将其传递给 register_fprobe()

通常,fprobe 数据结构使用 entry_handler 和/或 exit_handler 初始化,如下所示。

struct fprobe fp = {
       .entry_handler  = my_entry_callback,
       .exit_handler   = my_exit_callback,
};

要启用 fprobe,请调用 register_fprobe()register_fprobe_ips()register_fprobe_syms() 之一。这些函数使用不同类型的参数注册 fprobe。

register_fprobe() 通过函数名过滤器启用 fprobe。例如,这会在 “func*()” 函数上启用 @fp,除了 “func2()”。

register_fprobe(&fp, "func*", "func2");

register_fprobe_ips() 通过 ftrace 位置地址启用 fprobe。例如:

unsigned long ips[] = { 0x.... };

register_fprobe_ips(&fp, ips, ARRAY_SIZE(ips));

并且 register_fprobe_syms() 通过符号名启用 fprobe。例如:

char syms[] = {"func1", "func2", "func3"};

register_fprobe_syms(&fp, syms, ARRAY_SIZE(syms));

要禁用(从函数中删除)此 fprobe,请调用

unregister_fprobe(&fp);

您可以通过以下方式临时(软)禁用 fprobe

disable_fprobe(&fp);

并通过以下方式恢复

enable_fprobe(&fp);

以上定义通过包含头文件

#include <linux/fprobe.h>

与 ftrace 相同,注册的回调将在调用 register_fprobe() 之后和返回之前开始被调用。请参阅 Documentation/trace/ftrace.rst

此外,unregister_fprobe() 将保证在 unregister_fprobe() 返回后,函数不再调用进入和退出处理程序,与 unregister_ftrace_function() 相同。

fprobe 进入/退出处理程序

进入/退出回调函数的原型如下所示

int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct pt_regs *regs, void *entry_data);

void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct pt_regs *regs, void *entry_data);

请注意,@entry_ip 在函数入口处保存并传递给退出处理程序。如果进入回调函数返回 !0,则会取消相应的退出回调。

@fp

这是与此处理程序相关的 fprobe 数据结构的地址。您可以将 fprobe 嵌入到您的数据结构中,并通过 container_of() 宏从 @fp 获取它。@fp 不得为 NULL。

@entry_ip

这是被跟踪函数的 ftrace 地址(包括进入和退出)。请注意,这可能不是函数的实际入口地址,而是 ftrace 检测的地址。

@ret_ip

这是被跟踪函数将返回到的返回地址,位于调用方的某个位置。这可以在入口和出口处使用。

@regs

这是入口和出口处的 pt_regs 数据结构。请注意,@regs 的指令指针在 entry_handler 中可能与 @entry_ip 不同。如果您需要跟踪的指令指针,则需要使用 @entry_ip。另一方面,在 exit_handler 中,@regs 的指令指针设置为当前返回地址。

@entry_data

这是在进入和退出处理程序之间共享数据的本地存储。默认情况下,此存储为 NULL。如果用户在注册 fprobe 时指定了 exit_handler 字段和 entry_data_size 字段,则会分配存储空间并将其传递给 entry_handlerexit_handler

与 kprobes 共享回调

由于 fprobe(和 ftrace)的递归安全性与 kprobes 略有不同,如果用户想从 fprobe 和 kprobes 运行相同的代码,这可能会导致问题。

Kprobes 具有每个 CPU 的 'current_kprobe' 变量,该变量在所有情况下都保护 kprobe 处理程序免受递归的影响。另一方面,fprobe 仅使用 ftrace_test_recursion_trylock()。这允许中断上下文在 fprobe 用户处理程序正在运行时调用另一个(或相同的)fprobe。

如果公共回调代码有自己的递归检测,或者它可以处理不同上下文(正常/中断/NMI)中的递归,则这不是问题。但是,如果它依赖于 'current_kprobe' 递归锁,则必须检查 kprobe_running() 并使用 kprobe_busy_*() API。

Fprobe 具有 FPROBE_FL_KPROBE_SHARED 标志来执行此操作。如果您的公共回调代码将与 kprobes 共享,请在注册 fprobe 之前 设置 FPROBE_FL_KPROBE_SHARED,例如

fprobe.flags = FPROBE_FL_KPROBE_SHARED;

register_fprobe(&fprobe, "func*", NULL);

这将保护您的公共回调免受嵌套调用的影响。

未命中计数器

fprobe 数据结构具有与 kprobes 相同的 fprobe::nmissed 计数器字段。当以下情况发生时,此计数器会增加:

  • fprobe 无法获取 ftrace_recursion 锁。这通常意味着由其他 ftrace 用户跟踪的函数从 entry_handler 中调用。

  • 由于缺少 rethook(用于挂钩函数返回的影子堆栈),fprobe 无法设置函数退出。

fprobe::nmissed 字段在这两种情况下都会增加。因此,前者会跳过入口和出口回调,而后者会跳过出口回调,但在这两种情况下,计数器都会增加 1。

请注意,如果在注册 fprobe 时将 FTRACE_OPS_FL_RECURSION 和/或 FTRACE_OPS_FL_RCU 设置为 fprobe::ops::flags (ftrace_ops::flags),则此计数器可能无法正常工作,因为 ftrace 会跳过增加计数器的 fprobe 函数。

函数和结构体

struct fprobe

基于 ftrace 的探针。

定义:

struct fprobe {
#ifdef CONFIG_FUNCTION_TRACER;
    struct ftrace_ops       ops;
#endif;
    unsigned long           nmissed;
    unsigned int            flags;
    struct rethook          *rethook;
    size_t entry_data_size;
    int nr_maxactive;
    fprobe_entry_cb entry_handler;
    fprobe_exit_cb exit_handler;
};

成员

ops

ftrace_ops。

nmissed

缺少事件的计数器。

flags

状态标志。

rethook

rethook 数据结构。(内部数据)

entry_data_size

私有数据存储大小。

nr_maxactive

活动函数的最大数量。

entry_handler

用于函数进入的回调函数。

exit_handler

用于函数退出的回调函数。

void disable_fprobe(struct fprobe *fp)

禁用 fprobe

参数

struct fprobe *fp

要禁用的 fprobe。

描述

这将软禁用 fp。请注意,这不会从函数入口中删除 ftrace 挂钩。

void enable_fprobe(struct fprobe *fp)

启用 fprobe

参数

struct fprobe *fp

要启用的 fprobe。

描述

这将软启用 fp

int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)

通过模式将 fprobe 注册到 ftrace。

参数

struct fprobe *fp

要注册的 fprobe 数据结构。

const char *filter

被探测符号的通配符模式。

const char *notfilter

不被探测符号的通配符模式。

描述

fp 注册到 ftrace,以便在与 filter 匹配的符号上启用探测。如果 notfilter 不为 NULL,则不探测与 notfilter 匹配的符号。

如果 fp 成功注册,则返回 0,否则返回 -errno。

int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num)

通过地址将 fprobe 注册到 ftrace。

参数

struct fprobe *fp

要注册的 fprobe 数据结构。

unsigned long *addrs

目标 ftrace 位置地址的数组。

int num

addrs 的条目数。

描述

fp 注册到 ftrace,以便在 addrs 给定的地址上启用探测。addrs 必须是 ftrace 位置地址的地址,它可能是符号地址 + 架构相关的偏移量。如果您不确定这意味着什么,请使用其他注册函数。

如果 fp 成功注册,则返回 0,否则返回 -errno。

int register_fprobe_syms(struct fprobe *fp, const char **syms, int num)

通过符号将 fprobe 注册到 ftrace。

参数

struct fprobe *fp

要注册的 fprobe 数据结构。

const char **syms

目标符号的数组。

int num

syms 的条目数。

描述

fp 注册到 syms 数组给定的符号。如果您确定符号存在于内核中,这将非常有用。

如果 fp 成功注册,则返回 0,否则返回 -errno。

int unregister_fprobe(struct fprobe *fp)

从 ftrace 注销 fprobe。

参数

struct fprobe *fp

要注销的 fprobe 数据结构。

描述

注销 fprobe(并从函数入口中删除 ftrace 钩子)。

如果 fp 成功注销,则返回 0,否则返回 -errno。