ebpf:验证者在哪里打印它的消息?
ebpf: where verifier prints its messages?
验证器在哪里打印消息?我在 struct bpf_insn
中嵌入了一个简单的代码,我将其加载并附加为 BPF_PROG_TYPE_SOCKET_FILTER
类型:
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_EXIT_INSN(),
};
这段代码是故意弄错的(R0
退出前没有初始化)。 bpf_prog_load()
returns EACCESS
错误并且无法加载,这是预期的,但我想要验证程序消息(dmesg 或控制台中没有任何内容)。
尝试加载 eBPF 程序时,由加载程序将缓冲区传递给内核验证程序并稍后打印它 得到验证者的输出。
验证器将使用用户 space 程序提供的缓冲区,并在其中打印所有日志。除了极少数特定消息外,它 不会 将任何内容打印到内核日志或控制台(由您的 shell 处理,而不是直接由内核处理)。
让我们看一下您在评论中提到的 samples/bpf/sock_example.c
中的片段。
prog_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, prog, insns_cnt,
"GPL", 0, bpf_log_buf, BPF_LOG_BUF_SIZE);
if (prog_fd < 0) {
printf("failed to load prog '%s'\n", strerror(errno));
goto cleanup;
}
这是我们尝试加载程序的部分。我们从 libbpf 中调用 bpf_load_program()
,然后按顺序传递程序类型、指令、指令数、许可证字符串、一些与内核版本相关的标志,最后:一个空缓冲区和它的大小。大小 BPF_LOG_BUF_SIZE
是非空的(在 tools/lib/bpf/bpf
中定义为 (UINT32_MAX >> 8)
)。
函数 bpf_load_program()
会将所有这些信息(包括指向缓冲区的指针)传递给 bpf()
系统调用,后者将尝试加载程序。验证者将用日志填充缓冲区(无论加载是否成功,但请参阅底部的注释)。然后再次由加载程序使用这些日志。函数 bpf_load_program()
是低级的,它对缓冲区中验证者的日志 什么都不做 ,即使加载失败也是如此。它留给调用者处理或转储日志。您尝试 运行 的示例应用程序也什么都不做;因此,缓冲区未被使用,您不会在控制台中看到日志。
要查看日志,在您的情况下,您可能只需要转储此缓冲区。像下面这样简单的东西应该可以工作:
...
if (prog_fd < 0) {
printf("failed to load prog '%s'\n", strerror(errno));
printf("%s", bpf_log_buf);
goto cleanup;
}
注意:除了buffer和buffer的大小,loader还必须传递一个log_level
整数给验证者,告诉它应该使用什么级别的冗长。如果值为 0
,验证程序不会向缓冲区打印任何内容。在当前情况下,我们不直接处理 log_level
。 bpf_load_program()
也没有并将值设置为 0
, 但是 它最终在 libbpf 中调用 libbpf__bpf_prog_load()
。该函数尝试在不更改 log_level
的情况下第一次加载程序,但如果失败,它会重新尝试将 log_level
设置为 1
- 请参阅 Mark 在评论详情。 log_level
的不同值在 internal kernel headers 中定义,不属于用户 API,这意味着验证者关于日志详细程度的行为可能因内核版本而异。
验证器在哪里打印消息?我在 struct bpf_insn
中嵌入了一个简单的代码,我将其加载并附加为 BPF_PROG_TYPE_SOCKET_FILTER
类型:
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_EXIT_INSN(),
};
这段代码是故意弄错的(R0
退出前没有初始化)。 bpf_prog_load()
returns EACCESS
错误并且无法加载,这是预期的,但我想要验证程序消息(dmesg 或控制台中没有任何内容)。
尝试加载 eBPF 程序时,由加载程序将缓冲区传递给内核验证程序并稍后打印它 得到验证者的输出。
验证器将使用用户 space 程序提供的缓冲区,并在其中打印所有日志。除了极少数特定消息外,它 不会 将任何内容打印到内核日志或控制台(由您的 shell 处理,而不是直接由内核处理)。
让我们看一下您在评论中提到的 samples/bpf/sock_example.c
中的片段。
prog_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, prog, insns_cnt,
"GPL", 0, bpf_log_buf, BPF_LOG_BUF_SIZE);
if (prog_fd < 0) {
printf("failed to load prog '%s'\n", strerror(errno));
goto cleanup;
}
这是我们尝试加载程序的部分。我们从 libbpf 中调用 bpf_load_program()
,然后按顺序传递程序类型、指令、指令数、许可证字符串、一些与内核版本相关的标志,最后:一个空缓冲区和它的大小。大小 BPF_LOG_BUF_SIZE
是非空的(在 tools/lib/bpf/bpf
中定义为 (UINT32_MAX >> 8)
)。
函数 bpf_load_program()
会将所有这些信息(包括指向缓冲区的指针)传递给 bpf()
系统调用,后者将尝试加载程序。验证者将用日志填充缓冲区(无论加载是否成功,但请参阅底部的注释)。然后再次由加载程序使用这些日志。函数 bpf_load_program()
是低级的,它对缓冲区中验证者的日志 什么都不做 ,即使加载失败也是如此。它留给调用者处理或转储日志。您尝试 运行 的示例应用程序也什么都不做;因此,缓冲区未被使用,您不会在控制台中看到日志。
要查看日志,在您的情况下,您可能只需要转储此缓冲区。像下面这样简单的东西应该可以工作:
...
if (prog_fd < 0) {
printf("failed to load prog '%s'\n", strerror(errno));
printf("%s", bpf_log_buf);
goto cleanup;
}
注意:除了buffer和buffer的大小,loader还必须传递一个log_level
整数给验证者,告诉它应该使用什么级别的冗长。如果值为 0
,验证程序不会向缓冲区打印任何内容。在当前情况下,我们不直接处理 log_level
。 bpf_load_program()
也没有并将值设置为 0
, 但是 它最终在 libbpf 中调用 libbpf__bpf_prog_load()
。该函数尝试在不更改 log_level
的情况下第一次加载程序,但如果失败,它会重新尝试将 log_level
设置为 1
- 请参阅 Mark 在评论详情。 log_level
的不同值在 internal kernel headers 中定义,不属于用户 API,这意味着验证者关于日志详细程度的行为可能因内核版本而异。