Commit caec5495 authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'libbpf: support custom SEC() handlers'

Andrii Nakryiko says:

====================

Add ability for user applications and libraries to register custom BPF program
SEC() handlers. See patch #2 for examples where this is useful.

Patch #1 does some preliminary refactoring to allow exponsing program
init, preload, and attach callbacks as public API. It also establishes
a protocol to allow optional auto-attach behavior. This will also help the
case of sometimes auto-attachable uprobes.

v4->v5:
  - API documentation improvements (Daniel);
v3->v4:
  - init_fn -> prog_setup_fn, preload_fn -> prog_prepare_load_fn (Alexei);
v2->v3:
  - moved callbacks and cookie into OPTS struct (Alan);
  - added more test scenarios (Alan);
  - address most of Alan's feedback, but kept API name;
v1->v2:
  - resubmitting due to git send-email screw up.

Cc: Alan Maguire <alan.maguire@oracle.com>
====================
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents d59e3cba aa963bcb
This diff is collapsed.
......@@ -1328,6 +1328,115 @@ LIBBPF_API int bpf_linker__add_file(struct bpf_linker *linker,
LIBBPF_API int bpf_linker__finalize(struct bpf_linker *linker);
LIBBPF_API void bpf_linker__free(struct bpf_linker *linker);
/*
* Custom handling of BPF program's SEC() definitions
*/
struct bpf_prog_load_opts; /* defined in bpf.h */
/* Called during bpf_object__open() for each recognized BPF program. Callback
* can use various bpf_program__set_*() setters to adjust whatever properties
* are necessary.
*/
typedef int (*libbpf_prog_setup_fn_t)(struct bpf_program *prog, long cookie);
/* Called right before libbpf performs bpf_prog_load() to load BPF program
* into the kernel. Callback can adjust opts as necessary.
*/
typedef int (*libbpf_prog_prepare_load_fn_t)(struct bpf_program *prog,
struct bpf_prog_load_opts *opts, long cookie);
/* Called during skeleton attach or through bpf_program__attach(). If
* auto-attach is not supported, callback should return 0 and set link to
* NULL (it's not considered an error during skeleton attach, but it will be
* an error for bpf_program__attach() calls). On error, error should be
* returned directly and link set to NULL. On success, return 0 and set link
* to a valid struct bpf_link.
*/
typedef int (*libbpf_prog_attach_fn_t)(const struct bpf_program *prog, long cookie,
struct bpf_link **link);
struct libbpf_prog_handler_opts {
/* size of this struct, for forward/backward compatiblity */
size_t sz;
/* User-provided value that is passed to prog_setup_fn,
* prog_prepare_load_fn, and prog_attach_fn callbacks. Allows user to
* register one set of callbacks for multiple SEC() definitions and
* still be able to distinguish them, if necessary. For example,
* libbpf itself is using this to pass necessary flags (e.g.,
* sleepable flag) to a common internal SEC() handler.
*/
long cookie;
/* BPF program initialization callback (see libbpf_prog_setup_fn_t).
* Callback is optional, pass NULL if it's not necessary.
*/
libbpf_prog_setup_fn_t prog_setup_fn;
/* BPF program loading callback (see libbpf_prog_prepare_load_fn_t).
* Callback is optional, pass NULL if it's not necessary.
*/
libbpf_prog_prepare_load_fn_t prog_prepare_load_fn;
/* BPF program attach callback (see libbpf_prog_attach_fn_t).
* Callback is optional, pass NULL if it's not necessary.
*/
libbpf_prog_attach_fn_t prog_attach_fn;
};
#define libbpf_prog_handler_opts__last_field prog_attach_fn
/**
* @brief **libbpf_register_prog_handler()** registers a custom BPF program
* SEC() handler.
* @param sec section prefix for which custom handler is registered
* @param prog_type BPF program type associated with specified section
* @param exp_attach_type Expected BPF attach type associated with specified section
* @param opts optional cookie, callbacks, and other extra options
* @return Non-negative handler ID is returned on success. This handler ID has
* to be passed to *libbpf_unregister_prog_handler()* to unregister such
* custom handler. Negative error code is returned on error.
*
* *sec* defines which SEC() definitions are handled by this custom handler
* registration. *sec* can have few different forms:
* - if *sec* is just a plain string (e.g., "abc"), it will match only
* SEC("abc"). If BPF program specifies SEC("abc/whatever") it will result
* in an error;
* - if *sec* is of the form "abc/", proper SEC() form is
* SEC("abc/something"), where acceptable "something" should be checked by
* *prog_init_fn* callback, if there are additional restrictions;
* - if *sec* is of the form "abc+", it will successfully match both
* SEC("abc") and SEC("abc/whatever") forms;
* - if *sec* is NULL, custom handler is registered for any BPF program that
* doesn't match any of the registered (custom or libbpf's own) SEC()
* handlers. There could be only one such generic custom handler registered
* at any given time.
*
* All custom handlers (except the one with *sec* == NULL) are processed
* before libbpf's own SEC() handlers. It is allowed to "override" libbpf's
* SEC() handlers by registering custom ones for the same section prefix
* (i.e., it's possible to have custom SEC("perf_event/LLC-load-misses")
* handler).
*
* Note, like much of global libbpf APIs (e.g., libbpf_set_print(),
* libbpf_set_strict_mode(), etc)) these APIs are not thread-safe. User needs
* to ensure synchronization if there is a risk of running this API from
* multiple threads simultaneously.
*/
LIBBPF_API int libbpf_register_prog_handler(const char *sec,
enum bpf_prog_type prog_type,
enum bpf_attach_type exp_attach_type,
const struct libbpf_prog_handler_opts *opts);
/**
* @brief *libbpf_unregister_prog_handler()* unregisters previously registered
* custom BPF program SEC() handler.
* @param handler_id handler ID returned by *libbpf_register_prog_handler()*
* after successful registration
* @return 0 on success, negative error code if handler isn't found
*
* Note, like much of global libbpf APIs (e.g., libbpf_set_print(),
* libbpf_set_strict_mode(), etc)) these APIs are not thread-safe. User needs
* to ensure synchronization if there is a risk of running this API from
* multiple threads simultaneously.
*/
LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);
#ifdef __cplusplus
} /* extern "C" */
#endif
......
......@@ -439,3 +439,9 @@ LIBBPF_0.7.0 {
libbpf_probe_bpf_prog_type;
libbpf_set_memlock_rlim_max;
} LIBBPF_0.6.0;
LIBBPF_0.8.0 {
global:
libbpf_register_prog_handler;
libbpf_unregister_prog_handler;
} LIBBPF_0.7.0;
......@@ -4,6 +4,6 @@
#define __LIBBPF_VERSION_H
#define LIBBPF_MAJOR_VERSION 0
#define LIBBPF_MINOR_VERSION 7
#define LIBBPF_MINOR_VERSION 8
#endif /* __LIBBPF_VERSION_H */
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Facebook */
#include <test_progs.h>
#include "test_custom_sec_handlers.skel.h"
#define COOKIE_ABC1 1
#define COOKIE_ABC2 2
#define COOKIE_CUSTOM 3
#define COOKIE_FALLBACK 4
#define COOKIE_KPROBE 5
static int custom_setup_prog(struct bpf_program *prog, long cookie)
{
if (cookie == COOKIE_ABC1)
bpf_program__set_autoload(prog, false);
return 0;
}
static int custom_prepare_load_prog(struct bpf_program *prog,
struct bpf_prog_load_opts *opts, long cookie)
{
if (cookie == COOKIE_FALLBACK)
opts->prog_flags |= BPF_F_SLEEPABLE;
else if (cookie == COOKIE_ABC1)
ASSERT_FALSE(true, "unexpected preload for abc");
return 0;
}
static int custom_attach_prog(const struct bpf_program *prog, long cookie,
struct bpf_link **link)
{
switch (cookie) {
case COOKIE_ABC2:
*link = bpf_program__attach_raw_tracepoint(prog, "sys_enter");
return libbpf_get_error(*link);
case COOKIE_CUSTOM:
*link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_nanosleep");
return libbpf_get_error(*link);
case COOKIE_KPROBE:
case COOKIE_FALLBACK:
/* no auto-attach for SEC("xyz") and SEC("kprobe") */
*link = NULL;
return 0;
default:
ASSERT_FALSE(true, "unexpected cookie");
return -EINVAL;
}
}
static int abc1_id;
static int abc2_id;
static int custom_id;
static int fallback_id;
static int kprobe_id;
__attribute__((constructor))
static void register_sec_handlers(void)
{
LIBBPF_OPTS(libbpf_prog_handler_opts, abc1_opts,
.cookie = COOKIE_ABC1,
.prog_setup_fn = custom_setup_prog,
.prog_prepare_load_fn = custom_prepare_load_prog,
.prog_attach_fn = NULL,
);
LIBBPF_OPTS(libbpf_prog_handler_opts, abc2_opts,
.cookie = COOKIE_ABC2,
.prog_setup_fn = custom_setup_prog,
.prog_prepare_load_fn = custom_prepare_load_prog,
.prog_attach_fn = custom_attach_prog,
);
LIBBPF_OPTS(libbpf_prog_handler_opts, custom_opts,
.cookie = COOKIE_CUSTOM,
.prog_setup_fn = NULL,
.prog_prepare_load_fn = NULL,
.prog_attach_fn = custom_attach_prog,
);
abc1_id = libbpf_register_prog_handler("abc", BPF_PROG_TYPE_RAW_TRACEPOINT, 0, &abc1_opts);
abc2_id = libbpf_register_prog_handler("abc/", BPF_PROG_TYPE_RAW_TRACEPOINT, 0, &abc2_opts);
custom_id = libbpf_register_prog_handler("custom+", BPF_PROG_TYPE_TRACEPOINT, 0, &custom_opts);
}
__attribute__((destructor))
static void unregister_sec_handlers(void)
{
libbpf_unregister_prog_handler(abc1_id);
libbpf_unregister_prog_handler(abc2_id);
libbpf_unregister_prog_handler(custom_id);
}
void test_custom_sec_handlers(void)
{
LIBBPF_OPTS(libbpf_prog_handler_opts, opts,
.prog_setup_fn = custom_setup_prog,
.prog_prepare_load_fn = custom_prepare_load_prog,
.prog_attach_fn = custom_attach_prog,
);
struct test_custom_sec_handlers* skel;
int err;
ASSERT_GT(abc1_id, 0, "abc1_id");
ASSERT_GT(abc2_id, 0, "abc2_id");
ASSERT_GT(custom_id, 0, "custom_id");
/* override libbpf's handle of SEC("kprobe/...") but also allow pure
* SEC("kprobe") due to "kprobe+" specifier. Register it as
* TRACEPOINT, just for fun.
*/
opts.cookie = COOKIE_KPROBE;
kprobe_id = libbpf_register_prog_handler("kprobe+", BPF_PROG_TYPE_TRACEPOINT, 0, &opts);
/* fallback treats everything as BPF_PROG_TYPE_SYSCALL program to test
* setting custom BPF_F_SLEEPABLE bit in preload handler
*/
opts.cookie = COOKIE_FALLBACK;
fallback_id = libbpf_register_prog_handler(NULL, BPF_PROG_TYPE_SYSCALL, 0, &opts);
if (!ASSERT_GT(fallback_id, 0, "fallback_id") /* || !ASSERT_GT(kprobe_id, 0, "kprobe_id")*/) {
if (fallback_id > 0)
libbpf_unregister_prog_handler(fallback_id);
if (kprobe_id > 0)
libbpf_unregister_prog_handler(kprobe_id);
return;
}
/* open skeleton and validate assumptions */
skel = test_custom_sec_handlers__open();
if (!ASSERT_OK_PTR(skel, "skel_open"))
goto cleanup;
ASSERT_EQ(bpf_program__type(skel->progs.abc1), BPF_PROG_TYPE_RAW_TRACEPOINT, "abc1_type");
ASSERT_FALSE(bpf_program__autoload(skel->progs.abc1), "abc1_autoload");
ASSERT_EQ(bpf_program__type(skel->progs.abc2), BPF_PROG_TYPE_RAW_TRACEPOINT, "abc2_type");
ASSERT_EQ(bpf_program__type(skel->progs.custom1), BPF_PROG_TYPE_TRACEPOINT, "custom1_type");
ASSERT_EQ(bpf_program__type(skel->progs.custom2), BPF_PROG_TYPE_TRACEPOINT, "custom2_type");
ASSERT_EQ(bpf_program__type(skel->progs.kprobe1), BPF_PROG_TYPE_TRACEPOINT, "kprobe1_type");
ASSERT_EQ(bpf_program__type(skel->progs.xyz), BPF_PROG_TYPE_SYSCALL, "xyz_type");
skel->rodata->my_pid = getpid();
/* now attempt to load everything */
err = test_custom_sec_handlers__load(skel);
if (!ASSERT_OK(err, "skel_load"))
goto cleanup;
/* now try to auto-attach everything */
err = test_custom_sec_handlers__attach(skel);
if (!ASSERT_OK(err, "skel_attach"))
goto cleanup;
skel->links.xyz = bpf_program__attach(skel->progs.kprobe1);
ASSERT_EQ(errno, EOPNOTSUPP, "xyz_attach_err");
ASSERT_ERR_PTR(skel->links.xyz, "xyz_attach");
/* trigger programs */
usleep(1);
/* SEC("abc") is set to not auto-loaded */
ASSERT_FALSE(skel->bss->abc1_called, "abc1_called");
ASSERT_TRUE(skel->bss->abc2_called, "abc2_called");
ASSERT_TRUE(skel->bss->custom1_called, "custom1_called");
ASSERT_TRUE(skel->bss->custom2_called, "custom2_called");
/* SEC("kprobe") shouldn't be auto-attached */
ASSERT_FALSE(skel->bss->kprobe1_called, "kprobe1_called");
/* SEC("xyz") shouldn't be auto-attached */
ASSERT_FALSE(skel->bss->xyz_called, "xyz_called");
cleanup:
test_custom_sec_handlers__destroy(skel);
ASSERT_OK(libbpf_unregister_prog_handler(fallback_id), "unregister_fallback");
ASSERT_OK(libbpf_unregister_prog_handler(kprobe_id), "unregister_kprobe");
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
const volatile int my_pid;
bool abc1_called;
bool abc2_called;
bool custom1_called;
bool custom2_called;
bool kprobe1_called;
bool xyz_called;
SEC("abc")
int abc1(void *ctx)
{
abc1_called = true;
return 0;
}
SEC("abc/whatever")
int abc2(void *ctx)
{
abc2_called = true;
return 0;
}
SEC("custom")
int custom1(void *ctx)
{
custom1_called = true;
return 0;
}
SEC("custom/something")
int custom2(void *ctx)
{
custom2_called = true;
return 0;
}
SEC("kprobe")
int kprobe1(void *ctx)
{
kprobe1_called = true;
return 0;
}
SEC("xyz/blah")
int xyz(void *ctx)
{
int whatever;
/* use sleepable helper, custom handler should set sleepable flag */
bpf_copy_from_user(&whatever, sizeof(whatever), NULL);
xyz_called = true;
return 0;
}
char _license[] SEC("license") = "GPL";
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment