bpf-util: add helper to probe the kernel for a kfunc

Add bpf_kernel_has_kfunc() which checks whether the kernel's vmlinux BTF
advertises a function of a given name.

The three btf__* symbols it needs are optional in the dlopen table.
btf__load_vmlinux_btf() only exists since libbpf 0.5 and a libbpf that
lacks any of them should degrade this probe, not fail dlopen_bpf() and
with it all of BPF. Note, libbpf < 1.0 returns error pointers instead of
NULL, so the result is checked with bpf_get_error_translated().

Finding the name doesn't tell us all that much though. It tells us that
the function exists, not that it is registered as a kfunc for the program
type we care about. So callers still have to be prepared for the program
load to fail.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner
2026-08-10 16:09:06 +02:00
parent 43914520c1
commit cc54fb8f73
2 changed files with 51 additions and 2 deletions
+39 -2
View File
@@ -54,6 +54,9 @@ static int missing_bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *
return -ENOSYS;
}
DLSYM_PROTOTYPE(bpf_token_create) = missing_bpf_token_create;
DLSYM_PROTOTYPE(btf__find_by_name_kind) = NULL;
DLSYM_PROTOTYPE(btf__free) = NULL;
DLSYM_PROTOTYPE(btf__load_vmlinux_btf) = NULL;
DLSYM_PROTOTYPE(libbpf_set_print) = NULL;
DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL;
DLSYM_PROTOTYPE(ring_buffer__free) = NULL;
@@ -143,12 +146,18 @@ int dlopen_bpf(int log_level) {
"Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled.");
/* Version-specific symbols: bpf_create_map exists only in libbpf < 1.0; bpf_map_create and
* bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. Unresolved prototypes keep
* their initializers (NULL, or a fallback returning -ENOSYS for bpf_token_create). */
* bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. btf__load_vmlinux_btf only in
* 0.5+; its two btf__* companions are older, but keep the whole probe trio optional so a missing
* symbol degrades bpf_kernel_has_kfunc() instead of failing dlopen_bpf() entirely. Unresolved
* prototypes keep their initializers (NULL, or a fallback returning -ENOSYS for
* bpf_token_create). */
DLSYM_OPTIONAL(bpf_dl, bpf_create_map);
DLSYM_OPTIONAL(bpf_dl, bpf_map_create);
DLSYM_OPTIONAL(bpf_dl, bpf_object__next_map);
DLSYM_OPTIONAL(bpf_dl, bpf_token_create);
DLSYM_OPTIONAL(bpf_dl, btf__find_by_name_kind);
DLSYM_OPTIONAL(bpf_dl, btf__free);
DLSYM_OPTIONAL(bpf_dl, btf__load_vmlinux_btf);
/* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */
(void) sym_libbpf_set_print(bpf_print_func);
@@ -177,4 +186,32 @@ int bpf_get_error_translated(const void *ptr) {
return r;
}
}
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct btf *, sym_btf__free, NULL);
bool bpf_kernel_has_kfunc(const char *name) {
_cleanup_(sym_btf__freep) struct btf *btf = NULL;
int id, r;
assert(name);
if (dlopen_bpf(LOG_DEBUG) < 0)
return false;
if (!sym_btf__load_vmlinux_btf || !sym_btf__find_by_name_kind || !sym_btf__free) {
log_debug("libbpf too old to probe the kernel BTF for functions, assuming %s() is unavailable.", name);
return false;
}
btf = sym_btf__load_vmlinux_btf();
r = bpf_get_error_translated(btf); /* libbpf < 1.0 returns error pointers, not NULL; btf__free() copes with both */
if (r != 0) {
log_debug_errno(r, "Failed to load vmlinux BTF, assuming kfunc %s() is unavailable: %m", name);
return false;
}
id = sym_btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
log_debug("Kernel BTF %s %s().", id > 0 ? "provides" : "lacks", name);
return id > 0;
}
#endif
+12
View File
@@ -12,6 +12,7 @@
#endif
#include <bpf/bpf.h> /* IWYU pragma: export */
#include <bpf/btf.h> /* IWYU pragma: export */
#include <bpf/libbpf.h> /* IWYU pragma: export */
#include "dlfcn-util.h"
@@ -21,6 +22,7 @@
* - bpf_map_create, bpf_object__next_map: available since libbpf 0.7
* - bpf_token_create: available since libbpf 1.5
* - bpf_create_map: removed in libbpf 1.0
* - btf__load_vmlinux_btf: available since libbpf 0.5
*/
DISABLE_WARNING_REDUNDANT_DECLS;
/* NOLINTBEGIN(readability-redundant-declaration) */
@@ -30,6 +32,7 @@ extern int bpf_create_map(enum bpf_map_type, int key_size, int value_size, int m
extern int bpf_map_create(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *);
extern struct bpf_map* bpf_object__next_map(const struct bpf_object *obj, const struct bpf_map *map);
extern int bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts);
extern struct btf* btf__load_vmlinux_btf(void);
/* NOLINTEND(readability-redundant-declaration) */
REENABLE_WARNING;
@@ -65,6 +68,9 @@ extern DLSYM_PROTOTYPE(bpf_program__attach_lsm);
extern DLSYM_PROTOTYPE(bpf_program__name);
extern DLSYM_PROTOTYPE(bpf_program__set_autoload);
extern DLSYM_PROTOTYPE(bpf_token_create);
extern DLSYM_PROTOTYPE(btf__find_by_name_kind);
extern DLSYM_PROTOTYPE(btf__free);
extern DLSYM_PROTOTYPE(btf__load_vmlinux_btf);
extern DLSYM_PROTOTYPE(libbpf_set_print);
extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd);
extern DLSYM_PROTOTYPE(ring_buffer__free);
@@ -92,6 +98,12 @@ static inline int compat_bpf_map_create(
* this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos
* we understand. */
int bpf_get_error_translated(const void *ptr);
/* Probe the kernel's vmlinux BTF for a function of the given name, to check whether a BPF kfunc is
* available. This probes vmlinux BTF only, not module BTFs; and BTF advertising the function only proves
* it exists, not that it is registered as a kfunc for a given program type — callers must still handle
* program load failure. */
bool bpf_kernel_has_kfunc(const char *name);
#endif
int dlopen_bpf(int log_level) _dlopen_loader_;