在eBPF中,fentry和fexit通常用于监控和追踪内核函数的入口和出口。fentry
和fexit
,类似于kprobe
和kretprobe
。但是fentry和fexit的性能要比kprobe及kretprobe要好。
示例程序(有问题版)
bpf.c
...
static int entry(void *ctx){
// is your code
}
SEC("fentry/dummy_fentry")
int BPF_PROG(dummy_fentry)
{
entry(ctx);
return 0;
}
static int exit(void *ctx){
// is your code
}
SEC("fexit/dummy_fexit")
int BPF_PROG(dummy_fexit)
{
exit(ctx);
return 0;
}
用户态.c
static bool try_fentry(struct probe_bpf *skel, std::string func)
{
long err;
// 检查是否可以使用 fentry 附加到指定的内核函数上
if (!fentry_can_attach(func.c_str(), NULL))
{
return false;
}
// 尝试将 dummy_fentry 程序附加到指定的内核函数func
err = bpf_program__set_attach_target(skel->progs.dummy_fentry, 0, func.c_str());
if (err)
{
// 如果附加失败,禁用 dummy_fentry 和 dummy_fexit 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_fentry, false);
bpf_program__set_autoload(skel->progs.dummy_fexit, false);
return false;
}
// 尝试将 dummy_fexit 程序附加到指定的内核函数退出点
err = bpf_program__set_attach_target(skel->progs.dummy_fexit, 0, func.c_str());
if (err)
{
// 如果附加失败,禁用 dummy_fentry 和 dummy_fexit 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_fentry, false);
bpf_program__set_autoload(skel->progs.dummy_fexit, false);
return false;
}
// 如果 fentry 和 fexit 成功附加,禁用 dummy_kprobe 和 dummy_kretprobe 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_kprobe, false);
bpf_program__set_autoload(skel->progs.dummy_kretprobe, false);
return true;
}
static int attach_kprobes(struct probe_bpf *skel, std::string func)
{
skel->links.dummy_kprobe =
bpf_program__attach_kprobe(skel->progs.dummy_kprobe, false, func.c_str());
CHECK_ERR(!skel->links.dummy_kprobe, "Fail to attach kprobe");
skel->links.dummy_kretprobe =
bpf_program__attach_kprobe(skel->progs.dummy_kretprobe, true, func.c_str());
CHECK_ERR(!skel->links.dummy_kretprobe, "Fail to attach ketprobe");
return 0;
}
//load 前调用
bool tryf = try_fentry(skel, str);
//attach 部分调用
if (!tryf)
{
err = attach_kprobes(skel, func);
return 0;
}
运行结果
make编译之后,运行,效果如下:
动态挂载fentry及fexit
下面主要说一下如何动态挂载fentry
及fexit
。在梳理一遍我上面的用户态代码流程,发现是我只在加载前设置了bpf程序要加载到哪个函数,但是没attach。如下代码,我在attach
部分,先判断可以用fentry
和fexit
指定bpf
程序到对应的内核函数上吗,如果不可以则用attach kprobe
及kretprobe
。但如果可以呢?我没有attach
//load 前调用
bool tryf = try_fentry(skel, str);
//attach 部分调用
if (!tryf)
{
err = attach_kprobes(skel, func);
return 0;
}
用到的API
1.bpf_program__set_attach_target
bpf_program__set_attach_target
是一个用于设置 BPF
程序附加目标的函数。它通常用于附加 eBPF
程序到特定的内核函数上,特别是在使用 fentry
和 fexit
类型的 BPF
程序时。
注意:该函数设置附加目标时必须在bpf
程序load
之前。
函数原型:bpf_program__set_attach_target(struct bpf_program *prog,int attach_prog_fd, const char *attach_func_name)
函数参数详解:
- struct bpf_program *prog:这是一个指向 BPF 程序对象的指针,该参数指定了要附加到目标函数的 BPF 程序
- int attach_prog_fd:该文件描述符指定了要附加的目标程序或上下文
- const char *attach_func_name:该字符串表示 BPF 程序要附加的内核函数名
2.bpf_program__attach
bpf_program__attach
是一个用于将 BPF 程序附加到特定钩子(如内核函数、网络数据路径等)的函数。
函数原型:struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
参数详解
- const struct bpf_program *prog:这是一个指向 BPF 程序对象的指针,指定了要附加的 BPF 程序
示例程序(正确版)
bpf.c
...
static int entry(void *ctx){
// is your code
}
SEC("fentry/dummy_fentry")
int BPF_PROG(dummy_fentry)
{
entry(ctx);
return 0;
}
static int exit(void *ctx){
// is your code
}
SEC("fexit/dummy_fexit")
int BPF_PROG(dummy_fexit)
{
exit(ctx);
return 0;
}
用户态.c
static bool try_fentry(struct probe_bpf *skel, std::string func)
{
long err;
// 检查是否可以使用 fentry 附加到指定的内核函数上
if (!fentry_can_attach(func.c_str(), NULL))
{
return false;
}
// 尝试将 dummy_fentry 程序附加到指定的内核函数func
err = bpf_program__set_attach_target(skel->progs.dummy_fentry, 0, func.c_str());
if (err)
{
// 如果附加失败,禁用 dummy_fentry 和 dummy_fexit 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_fentry, false);
bpf_program__set_autoload(skel->progs.dummy_fexit, false);
return false;
}
// 尝试将 dummy_fexit 程序附加到指定的内核函数退出点
err = bpf_program__set_attach_target(skel->progs.dummy_fexit, 0, func.c_str());
if (err)
{
// 如果附加失败,禁用 dummy_fentry 和 dummy_fexit 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_fentry, false);
bpf_program__set_autoload(skel->progs.dummy_fexit, false);
return false;
}
// 如果 fentry 和 fexit 成功附加,禁用 dummy_kprobe 和 dummy_kretprobe 程序的自动加载
bpf_program__set_autoload(skel->progs.dummy_kprobe, false);
bpf_program__set_autoload(skel->progs.dummy_kretprobe, false);
return true;
}
static int attach_kprobes(struct probe_bpf *skel, std::string func)
{
skel->links.dummy_kprobe =
bpf_program__attach_kprobe(skel->progs.dummy_kprobe, false, func.c_str());
CHECK_ERR(!skel->links.dummy_kprobe, "Fail to attach kprobe");
skel->links.dummy_kretprobe =
bpf_program__attach_kprobe(skel->progs.dummy_kretprobe, true, func.c_str());
CHECK_ERR(!skel->links.dummy_kretprobe, "Fail to attach ketprobe");
return 0;
}
static int attach_fentry(struct probe_bpf *skel)
{
skel->links.dummy_fentry =
bpf_program__attach(skel->progs.dummy_fentry);
CHECK_ERR(!skel->links.dummy_fentry, "Fail to attach fentry");
skel->links.dummy_fexit =
bpf_program__attach(skel->progs.dummy_fexit);
CHECK_ERR(!skel->links.dummy_fexit, "Fail to attach fexit");
return 0;
}
//load 前调用
bool tryf = try_fentry(skel, str);
//attach 部分调用
if (!tryf)
{
err = attach_kprobes(skel, func);
return 0;
}
else
{
err = attach_fentry(skel);
return 0;
}
函数 try_fentry
试图将一个 eBPF
程序附加到指定的内核函数上,首先尝试使用 fentry
(函数入口点) 和 fexit
(函数退出点) 方法。如果这些方法失败,则禁用自动加载对应的 eBPF
程序,然后使用kprobe、kretprobe
来加载。
运行效果
统计时延: