EBPF 程序加载失败,没有验证程序日志
EBPF program load fails without verifier log
我正在尝试编写一个 EBPF 程序,但在某些地方我卡住了。在文档中说,由您定义的函数是绝对有效和可调用的,但是,即使在这个最简单的示例中(尽管使用 map),我在使用 tc
将程序加载到接口时也会出错,并且验证者绝对不会发出任何东西。
这是我的示例程序,我还插入了必要的BPF-helpers,以便立即编译:
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#ifndef __BPF_HELPERS_H
#define __BPF_HELPERS_H
/* helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by elf_bpf loader
*/
#define SEC(NAME) __attribute__((section(NAME), used))
/* a helper structure used by eBPF C program
* to describe map attributes to elf_bpf loader
*/
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int inner_map_idx;
unsigned int numa_node;
};
/* helper functions called from eBPF programs written in C */
static void *(*bpf_map_lookup_elem)(void *map, const void *key) =
(void *) BPF_FUNC_map_lookup_elem;
static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) =
(void *) BPF_FUNC_clone_redirect;
#endif
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(int),
.max_entries = 256,
};
static int foo(int value) { return value == 1; }
SEC("action")
int filter_and_redirect(struct __sk_buff* skb)
{
int key = 0;
int *value = (int*)bpf_map_lookup_elem(&my_map, &key);
if (!value) return TC_ACT_SHOT;
if (foo(*value)) bpf_clone_redirect(skb, 1, 1);
// if (*value == 1) bpf_clone_redirect(skb, 1, 1);
return TC_ACT_SHOT;
}
SEC("classifier")
int cls_main(struct __sk_buff* skb) { return -1; }
我用下面的命令编译的,很标准
clang -O2 -fno-inline-functions -emit-llvm -c example.c -o - | llc -march=bpf -filetype=obj -o example.o
然后通过
加载到界面
sudo tc filter add dev foo0 parent ffff: bpf obj example.o sec classifier flowid ffff:1 action bpf obj example.o sec action ok
如果我用函数调用注释行并取消注释下一个,它会完美运行。但是用原程序加载的结果是:
Error fetching program/map!
bad action parsing
parse_action: bad value (6:bpf)!
Illegal "action"
应该有一些日志,具体说明为什么我的程序对验证程序无效,但没有,所以我真的被卡住了。为什么它不允许我使用该功能?谢谢大家的建议!
如果您想查看 llvm-objdump -S
命令的结果,可以找到 here.
[编辑]
从接受的答案看来,tc
中有一个错误(?),它基本上不允许不使用地图的功能。这就是为什么作为临时解决方法,我将示例中的函数更改为:
struct bpf_map_def SEC("maps") dummy_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 1,
};
int foo(int value) {
int key = 0;
int *v = (int*)bpf_map_lookup_elem(&dummy_map, &key);
return value == 1;
}
现在它可以正常工作,但是需要额外的地图操作成本,这不是必需的,所以我期待有关此问题的更多消息。
验证器没有日志,也不应该有任何日志,因为验证器永远没有机会检查您的程序。您收到的错误 来自 tc,它无法将字节码整形为内核程序,并且没有尝试将程序加载到内核中。
您可以检查 strace -e bpf tc filter ...
,没有 bpf(BPF_PROG_LOAD, ...)
被调用。
它失败是因为函数调用,而且看起来像是 tc 中的错误。
Tc(和 iproute2)在 commit b5cb33aec65c 中获得了对 eBPF 到 eBPF 函数调用的支持(“bpf:实现 bpf 到 bpf 调用支持”)。提交日志提到,为了添加支持,
First step is processing of map related relocation entries
for .text section
这转化为:
@@ -2120,10 +2192,18 @@ static int bpf_fetch_prog_relo(struct bpf_elf_ctx *ctx, const char *section,
static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section)
{
bool lderr = false, sseen = false;
+ struct bpf_elf_prog prog;
int ret = -1;
- if (bpf_has_map_data(ctx))
- ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen);
+ if (bpf_has_call_data(ctx)) {
+ ret = bpf_fetch_prog_relo(ctx, ".text", &lderr, NULL,
+ &ctx->prog_text);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (bpf_has_map_data(ctx) || bpf_has_call_data(ctx))
+ ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen, &prog);
if (ret < 0 && !lderr)
ret = bpf_fetch_prog(ctx, section, &sseen);
if (ret < 0 && !sseen)
也就是说调用bpf_fetch_prog_relo()
函数对.text
段进行了重定位操作,你的函数foo
所在的段。这样做是为了插入函数 (foo
) 使用的映射的文件描述符(如果有的话)。如果有的话?好吧,不,事实上它似乎一直被称为 ,即使 .text
函数不使用映射并且不需要重定位。但是如果找到重定位信息失败(如果没有重定位就是这种情况,因为专用部分将丢失),那么我们将退出并向上传播一个错误。回溯:
bpf_fetch_prog_relo()
bpf_fetch_prog_sec()
bpf_obj_open()
bpf_do_load()
bpf_load_common()
bpf_parse_and_load_common()
bpf_parse_opt()
parse_action()
bpf_parse_opt()
tc_filter_modify()
do_filter()
do_cmd()
main()
这最终使 tc 命令失败,parse_action()
等函数和其他一些函数会打印您看到的错误消息。同样,与内核验证程序无关。
如何解决?如果我是正确的并且这个 是 iproute2 中的一个错误,这应该在上游修复,我会和作者一起看看他是怎么想的。您可以修补 iproute2 或找到一种在您调用的函数中使用地图的方法。我使用以下命令成功加载了您的程序:
static int foo(int value)
{
int key = 0;
int *beep;
beep = (int*)bpf_map_lookup_elem(&my_map, &key);
if (!beep)
return 0;
return value == *beep;
}
(顺便感谢独立复制器,非常感谢。)这个例子不是很有用,但它似乎证实了 .text
.
的重定位是强制性的
我正在尝试编写一个 EBPF 程序,但在某些地方我卡住了。在文档中说,由您定义的函数是绝对有效和可调用的,但是,即使在这个最简单的示例中(尽管使用 map),我在使用 tc
将程序加载到接口时也会出错,并且验证者绝对不会发出任何东西。
这是我的示例程序,我还插入了必要的BPF-helpers,以便立即编译:
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#ifndef __BPF_HELPERS_H
#define __BPF_HELPERS_H
/* helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by elf_bpf loader
*/
#define SEC(NAME) __attribute__((section(NAME), used))
/* a helper structure used by eBPF C program
* to describe map attributes to elf_bpf loader
*/
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int inner_map_idx;
unsigned int numa_node;
};
/* helper functions called from eBPF programs written in C */
static void *(*bpf_map_lookup_elem)(void *map, const void *key) =
(void *) BPF_FUNC_map_lookup_elem;
static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) =
(void *) BPF_FUNC_clone_redirect;
#endif
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(int),
.max_entries = 256,
};
static int foo(int value) { return value == 1; }
SEC("action")
int filter_and_redirect(struct __sk_buff* skb)
{
int key = 0;
int *value = (int*)bpf_map_lookup_elem(&my_map, &key);
if (!value) return TC_ACT_SHOT;
if (foo(*value)) bpf_clone_redirect(skb, 1, 1);
// if (*value == 1) bpf_clone_redirect(skb, 1, 1);
return TC_ACT_SHOT;
}
SEC("classifier")
int cls_main(struct __sk_buff* skb) { return -1; }
我用下面的命令编译的,很标准
clang -O2 -fno-inline-functions -emit-llvm -c example.c -o - | llc -march=bpf -filetype=obj -o example.o
然后通过
加载到界面sudo tc filter add dev foo0 parent ffff: bpf obj example.o sec classifier flowid ffff:1 action bpf obj example.o sec action ok
如果我用函数调用注释行并取消注释下一个,它会完美运行。但是用原程序加载的结果是:
Error fetching program/map!
bad action parsing
parse_action: bad value (6:bpf)!
Illegal "action"
应该有一些日志,具体说明为什么我的程序对验证程序无效,但没有,所以我真的被卡住了。为什么它不允许我使用该功能?谢谢大家的建议!
如果您想查看 llvm-objdump -S
命令的结果,可以找到 here.
[编辑]
从接受的答案看来,tc
中有一个错误(?),它基本上不允许不使用地图的功能。这就是为什么作为临时解决方法,我将示例中的函数更改为:
struct bpf_map_def SEC("maps") dummy_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 1,
};
int foo(int value) {
int key = 0;
int *v = (int*)bpf_map_lookup_elem(&dummy_map, &key);
return value == 1;
}
现在它可以正常工作,但是需要额外的地图操作成本,这不是必需的,所以我期待有关此问题的更多消息。
验证器没有日志,也不应该有任何日志,因为验证器永远没有机会检查您的程序。您收到的错误 来自 tc,它无法将字节码整形为内核程序,并且没有尝试将程序加载到内核中。
您可以检查 strace -e bpf tc filter ...
,没有 bpf(BPF_PROG_LOAD, ...)
被调用。
它失败是因为函数调用,而且看起来像是 tc 中的错误。
Tc(和 iproute2)在 commit b5cb33aec65c 中获得了对 eBPF 到 eBPF 函数调用的支持(“bpf:实现 bpf 到 bpf 调用支持”)。提交日志提到,为了添加支持,
First step is processing of map related relocation entries
for .text section
这转化为:
@@ -2120,10 +2192,18 @@ static int bpf_fetch_prog_relo(struct bpf_elf_ctx *ctx, const char *section,
static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section)
{
bool lderr = false, sseen = false;
+ struct bpf_elf_prog prog;
int ret = -1;
- if (bpf_has_map_data(ctx))
- ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen);
+ if (bpf_has_call_data(ctx)) {
+ ret = bpf_fetch_prog_relo(ctx, ".text", &lderr, NULL,
+ &ctx->prog_text);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (bpf_has_map_data(ctx) || bpf_has_call_data(ctx))
+ ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen, &prog);
if (ret < 0 && !lderr)
ret = bpf_fetch_prog(ctx, section, &sseen);
if (ret < 0 && !sseen)
也就是说调用bpf_fetch_prog_relo()
函数对.text
段进行了重定位操作,你的函数foo
所在的段。这样做是为了插入函数 (foo
) 使用的映射的文件描述符(如果有的话)。如果有的话?好吧,不,事实上它似乎一直被称为 ,即使 .text
函数不使用映射并且不需要重定位。但是如果找到重定位信息失败(如果没有重定位就是这种情况,因为专用部分将丢失),那么我们将退出并向上传播一个错误。回溯:
bpf_fetch_prog_relo()
bpf_fetch_prog_sec()
bpf_obj_open()
bpf_do_load()
bpf_load_common()
bpf_parse_and_load_common()
bpf_parse_opt()
parse_action()
bpf_parse_opt()
tc_filter_modify()
do_filter()
do_cmd()
main()
这最终使 tc 命令失败,parse_action()
等函数和其他一些函数会打印您看到的错误消息。同样,与内核验证程序无关。
如何解决?如果我是正确的并且这个 是 iproute2 中的一个错误,这应该在上游修复,我会和作者一起看看他是怎么想的。您可以修补 iproute2 或找到一种在您调用的函数中使用地图的方法。我使用以下命令成功加载了您的程序:
static int foo(int value)
{
int key = 0;
int *beep;
beep = (int*)bpf_map_lookup_elem(&my_map, &key);
if (!beep)
return 0;
return value == *beep;
}
(顺便感谢独立复制器,非常感谢。)这个例子不是很有用,但它似乎证实了 .text
.