Fprobe - 函数进入/退出探针¶
简介¶
Fprobe 是一个基于 ftrace 中的函数图(function-graph)跟踪功能的函数进入/退出探针。如果你想在特定函数的进入和退出时附加回调,类似于 kprobes 和 kretprobes,但又不想跟踪所有函数,可以使用 fprobe。与 kprobes 和 kretprobes 相比,fprobe 能够通过单个处理程序(handler)为多个函数提供更快的插桩(instrumentation)。本文档描述了如何使用 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。例如,这会在除了 “func2()” 之外的 “func*()” 函数上启用 @fp。
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() 之后的一段时间内、且在其返回之前开始被调用。参见 ftrace - 函数跟踪器。
此外,与 unregister_ftrace_function() 类似,unregister_fprobe() 将保证在 unregister_fprobe() 返回后,进入和退出处理程序将不再被函数调用。
fprobe 进入/退出处理程序¶
进入/退出回调函数的原型如下
int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
请注意,@entry_ip 在函数进入时保存并传递给退出处理程序。如果进入回调函数返回非 0(!0),则相应的退出回调将被取消。
- @fp
这是与此处理程序相关的 fprobe 数据结构的地址。你可以将 fprobe 嵌入到你的数据结构中,并通过 @fp 使用
container_of()宏获取它。@fp 绝不能为 NULL。- @entry_ip
这是被跟踪函数的 ftrace 地址(进入和退出时都是如此)。注意,这可能不是函数的实际入口地址,而是 ftrace 进行插桩的地址。
- @ret_ip
这是被跟踪函数将要返回的返回地址,位于调用者内部的某处。这可以在进入和退出时使用。
- @fregs
这是进入和退出时的 ftrace_regs 数据结构。它包含函数参数或返回值。因此用户可以通过适当的 ftrace_regs_* API 访问这些值。
- @entry_data
这是一个用于在进入和退出处理程序之间共享数据的本地存储。此存储默认为 NULL。如果用户在注册 fprobe 时指定了 exit_handler 字段和 entry_data_size 字段,则会分配该存储空间并将其传递给 entry_handler 和 exit_handler。
同一函数上的进入数据大小和退出处理程序¶
由于进入数据是通过每个任务的栈(per-task stack)传递的且其大小有限,因此每个探针的进入数据大小限制为 15 * sizeof(long)。你还需注意,如果不同的 fprobe 正在探测同一个函数,此限制会变小。进入数据大小按 sizeof(long) 对齐,并且每个带有退出处理程序的 fprobe 都在栈上占用一个 sizeof(long) 的空间,因此你应该尽量减少在同一个函数上的 fprobe 数量。
丢失计数器¶
与 kprobes 类似,fprobe 数据结构具有 fprobe::nmissed 计数器字段。当以下情况发生时,此计数器会递增:
fprobe 获取 ftrace_recursion 锁失败。这通常意味着从 entry_handler 中调用了一个被其他 ftrace 用户跟踪的函数。
fprobe 由于无法从每个任务的影子栈(shadow stack)分配数据缓冲区,而无法设置函数退出。
fprobe::nmissed 字段在这两种情况下都会递增。因此,前者会跳过进入和退出回调,而后者会跳过退出回调,但这两种情况下的计数器都会增加 1。
请注意,如果在注册 fprobe 时将 FTRACE_OPS_FL_RECURSION 和/或 FTRACE_OPS_FL_RCU 设置为 fprobe::ops::flags (ftrace_ops::flags),此计数器可能无法正常工作,因为 ftrace 会跳过那些会使计数器增加的 fprobe 函数。
函数和结构¶
-
struct fprobe_hlist_node¶
用于 fprobe 的基于地址的哈希链表节点。
定义:
struct fprobe_hlist_node {
struct rhlist_head hlist;
unsigned long addr;
struct fprobe *fp;
};
成员
hlist用于地址搜索哈希表的 hlist 节点。
addrfp 的探测地址之一。
fp拥有此节点的 fprobe。
-
struct fprobe_hlist¶
fprobe 的哈希链表节点。
定义:
struct fprobe_hlist {
struct hlist_node hlist;
struct rcu_head rcu;
struct fprobe *fp;
int size;
struct fprobe_hlist_node array[];
};
成员
hlist用于存在性检查哈希表的 hlist 节点。
rcu用于 RCU 延迟释放的 rcu_head。
fp拥有此 fprobe_hlist 的 fprobe。
sizearray 的大小。
array用于每个要探测地址的 fprobe_hlist_node。
-
struct fprobe¶
基于 ftrace 的探针。
定义:
struct fprobe {
unsigned long nmissed;
unsigned int flags;
size_t entry_data_size;
fprobe_entry_cb entry_handler;
fprobe_exit_cb exit_handler;
struct fprobe_hlist *hlist_array;
};
成员
nmissed丢失事件的计数器。
flags状态标志。
entry_data_size私有数据存储大小。
entry_handler函数进入的回调函数。
exit_handler函数退出的回调函数。
hlist_array用于从 IP 哈希表中搜索 fprobe 的 fprobe_hlist。
参数
struct fprobe *fp要禁用的 fprobe。
描述
这将软禁用(soft-disable)fp。注意,这不会从函数入口移除 ftrace 钩子(hooks)。
参数
struct fprobe *fp要启用的 fprobe。
描述
这将软启用(soft-enable)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。
参数
struct fprobe *fp要注册的 fprobe 数据结构。
unsigned long *addrs目标函数地址数组。
int numaddrs 的条目数。
描述
将 fp 注册到 ftrace,以便在 addrs 给定的地址上启用探测。addrs 必须是 ftrace 位置地址的地址,这可能是符号地址加上架构相关的偏移量。如果你不确定这意味着什么,请使用其他注册函数。
如果 fp 注册成功则返回 0,否则返回 -errno。
参数
struct fprobe *fp要注册的 fprobe 数据结构。
const char **syms目标符号数组。
int numsyms 的条目数。
描述
将 fp 注册到 syms 数组给定的符号。如果你确信这些符号存在于内核中,这将非常有用。
如果 fp 注册成功则返回 0,否则返回 -errno。
参数
struct fprobe *fp要注销的 fprobe 数据结构。
描述
注销 fprobe(并从函数入口移除 ftrace 钩子)。此函数不会等待 fprobe 不再被使用。
如果 fp 注销成功则返回 0,否则返回 -errno。
参数
struct fprobe *fp要注销的 fprobe 数据结构。
描述
注销 fprobe(并从函数入口移除 ftrace 钩子)。此函数将阻塞,直到 fprobe 不再被使用。
如果 fp 注销成功则返回 0,否则返回 -errno。