mirror of
https://github.com/torvalds/linux.git
synced 2026-05-23 14:42:08 +02:00
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: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
caec549534
|
|
@ -201,12 +201,6 @@ struct reloc_desc {
|
|||
};
|
||||
};
|
||||
|
||||
struct bpf_sec_def;
|
||||
|
||||
typedef int (*init_fn_t)(struct bpf_program *prog, long cookie);
|
||||
typedef int (*preload_fn_t)(struct bpf_program *prog, struct bpf_prog_load_opts *opts, long cookie);
|
||||
typedef struct bpf_link *(*attach_fn_t)(const struct bpf_program *prog, long cookie);
|
||||
|
||||
/* stored as sec_def->cookie for all libbpf-supported SEC()s */
|
||||
enum sec_def_flags {
|
||||
SEC_NONE = 0,
|
||||
|
|
@ -234,14 +228,15 @@ enum sec_def_flags {
|
|||
};
|
||||
|
||||
struct bpf_sec_def {
|
||||
const char *sec;
|
||||
char *sec;
|
||||
enum bpf_prog_type prog_type;
|
||||
enum bpf_attach_type expected_attach_type;
|
||||
long cookie;
|
||||
int handler_id;
|
||||
|
||||
init_fn_t init_fn;
|
||||
preload_fn_t preload_fn;
|
||||
attach_fn_t attach_fn;
|
||||
libbpf_prog_setup_fn_t prog_setup_fn;
|
||||
libbpf_prog_prepare_load_fn_t prog_prepare_load_fn;
|
||||
libbpf_prog_attach_fn_t prog_attach_fn;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -6572,9 +6567,9 @@ static int bpf_object__sanitize_prog(struct bpf_object *obj, struct bpf_program
|
|||
static int libbpf_find_attach_btf_id(struct bpf_program *prog, const char *attach_name,
|
||||
int *btf_obj_fd, int *btf_type_id);
|
||||
|
||||
/* this is called as prog->sec_def->preload_fn for libbpf-supported sec_defs */
|
||||
static int libbpf_preload_prog(struct bpf_program *prog,
|
||||
struct bpf_prog_load_opts *opts, long cookie)
|
||||
/* this is called as prog->sec_def->prog_prepare_load_fn for libbpf-supported sec_defs */
|
||||
static int libbpf_prepare_prog_load(struct bpf_program *prog,
|
||||
struct bpf_prog_load_opts *opts, long cookie)
|
||||
{
|
||||
enum sec_def_flags def = cookie;
|
||||
|
||||
|
|
@ -6670,8 +6665,8 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog
|
|||
load_attr.fd_array = obj->fd_array;
|
||||
|
||||
/* adjust load_attr if sec_def provides custom preload callback */
|
||||
if (prog->sec_def && prog->sec_def->preload_fn) {
|
||||
err = prog->sec_def->preload_fn(prog, &load_attr, prog->sec_def->cookie);
|
||||
if (prog->sec_def && prog->sec_def->prog_prepare_load_fn) {
|
||||
err = prog->sec_def->prog_prepare_load_fn(prog, &load_attr, prog->sec_def->cookie);
|
||||
if (err < 0) {
|
||||
pr_warn("prog '%s': failed to prepare load attributes: %d\n",
|
||||
prog->name, err);
|
||||
|
|
@ -6971,8 +6966,8 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object
|
|||
/* sec_def can have custom callback which should be called
|
||||
* after bpf_program is initialized to adjust its properties
|
||||
*/
|
||||
if (prog->sec_def->init_fn) {
|
||||
err = prog->sec_def->init_fn(prog, prog->sec_def->cookie);
|
||||
if (prog->sec_def->prog_setup_fn) {
|
||||
err = prog->sec_def->prog_setup_fn(prog, prog->sec_def->cookie);
|
||||
if (err < 0) {
|
||||
pr_warn("prog '%s': failed to initialize: %d\n",
|
||||
prog->name, err);
|
||||
|
|
@ -8589,20 +8584,20 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log
|
|||
}
|
||||
|
||||
#define SEC_DEF(sec_pfx, ptype, atype, flags, ...) { \
|
||||
.sec = sec_pfx, \
|
||||
.sec = (char *)sec_pfx, \
|
||||
.prog_type = BPF_PROG_TYPE_##ptype, \
|
||||
.expected_attach_type = atype, \
|
||||
.cookie = (long)(flags), \
|
||||
.preload_fn = libbpf_preload_prog, \
|
||||
.prog_prepare_load_fn = libbpf_prepare_prog_load, \
|
||||
__VA_ARGS__ \
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cookie);
|
||||
static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie);
|
||||
static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cookie);
|
||||
static struct bpf_link *attach_trace(const struct bpf_program *prog, long cookie);
|
||||
static struct bpf_link *attach_lsm(const struct bpf_program *prog, long cookie);
|
||||
static struct bpf_link *attach_iter(const struct bpf_program *prog, long cookie);
|
||||
static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_link **link);
|
||||
|
||||
static const struct bpf_sec_def section_defs[] = {
|
||||
SEC_DEF("socket", SOCKET_FILTER, 0, SEC_NONE | SEC_SLOPPY_PFX),
|
||||
|
|
@ -8682,61 +8677,167 @@ static const struct bpf_sec_def section_defs[] = {
|
|||
SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX),
|
||||
};
|
||||
|
||||
#define MAX_TYPE_NAME_SIZE 32
|
||||
static size_t custom_sec_def_cnt;
|
||||
static struct bpf_sec_def *custom_sec_defs;
|
||||
static struct bpf_sec_def custom_fallback_def;
|
||||
static bool has_custom_fallback_def;
|
||||
|
||||
static int last_custom_sec_def_handler_id;
|
||||
|
||||
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)
|
||||
{
|
||||
struct bpf_sec_def *sec_def;
|
||||
|
||||
if (!OPTS_VALID(opts, libbpf_prog_handler_opts))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
||||
if (last_custom_sec_def_handler_id == INT_MAX) /* prevent overflow */
|
||||
return libbpf_err(-E2BIG);
|
||||
|
||||
if (sec) {
|
||||
sec_def = libbpf_reallocarray(custom_sec_defs, custom_sec_def_cnt + 1,
|
||||
sizeof(*sec_def));
|
||||
if (!sec_def)
|
||||
return libbpf_err(-ENOMEM);
|
||||
|
||||
custom_sec_defs = sec_def;
|
||||
sec_def = &custom_sec_defs[custom_sec_def_cnt];
|
||||
} else {
|
||||
if (has_custom_fallback_def)
|
||||
return libbpf_err(-EBUSY);
|
||||
|
||||
sec_def = &custom_fallback_def;
|
||||
}
|
||||
|
||||
sec_def->sec = sec ? strdup(sec) : NULL;
|
||||
if (sec && !sec_def->sec)
|
||||
return libbpf_err(-ENOMEM);
|
||||
|
||||
sec_def->prog_type = prog_type;
|
||||
sec_def->expected_attach_type = exp_attach_type;
|
||||
sec_def->cookie = OPTS_GET(opts, cookie, 0);
|
||||
|
||||
sec_def->prog_setup_fn = OPTS_GET(opts, prog_setup_fn, NULL);
|
||||
sec_def->prog_prepare_load_fn = OPTS_GET(opts, prog_prepare_load_fn, NULL);
|
||||
sec_def->prog_attach_fn = OPTS_GET(opts, prog_attach_fn, NULL);
|
||||
|
||||
sec_def->handler_id = ++last_custom_sec_def_handler_id;
|
||||
|
||||
if (sec)
|
||||
custom_sec_def_cnt++;
|
||||
else
|
||||
has_custom_fallback_def = true;
|
||||
|
||||
return sec_def->handler_id;
|
||||
}
|
||||
|
||||
int libbpf_unregister_prog_handler(int handler_id)
|
||||
{
|
||||
struct bpf_sec_def *sec_defs;
|
||||
int i;
|
||||
|
||||
if (handler_id <= 0)
|
||||
return libbpf_err(-EINVAL);
|
||||
|
||||
if (has_custom_fallback_def && custom_fallback_def.handler_id == handler_id) {
|
||||
memset(&custom_fallback_def, 0, sizeof(custom_fallback_def));
|
||||
has_custom_fallback_def = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < custom_sec_def_cnt; i++) {
|
||||
if (custom_sec_defs[i].handler_id == handler_id)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == custom_sec_def_cnt)
|
||||
return libbpf_err(-ENOENT);
|
||||
|
||||
free(custom_sec_defs[i].sec);
|
||||
for (i = i + 1; i < custom_sec_def_cnt; i++)
|
||||
custom_sec_defs[i - 1] = custom_sec_defs[i];
|
||||
custom_sec_def_cnt--;
|
||||
|
||||
/* try to shrink the array, but it's ok if we couldn't */
|
||||
sec_defs = libbpf_reallocarray(custom_sec_defs, custom_sec_def_cnt, sizeof(*sec_defs));
|
||||
if (sec_defs)
|
||||
custom_sec_defs = sec_defs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sec_def_matches(const struct bpf_sec_def *sec_def, const char *sec_name,
|
||||
bool allow_sloppy)
|
||||
{
|
||||
size_t len = strlen(sec_def->sec);
|
||||
|
||||
/* "type/" always has to have proper SEC("type/extras") form */
|
||||
if (sec_def->sec[len - 1] == '/') {
|
||||
if (str_has_pfx(sec_name, sec_def->sec))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* "type+" means it can be either exact SEC("type") or
|
||||
* well-formed SEC("type/extras") with proper '/' separator
|
||||
*/
|
||||
if (sec_def->sec[len - 1] == '+') {
|
||||
len--;
|
||||
/* not even a prefix */
|
||||
if (strncmp(sec_name, sec_def->sec, len) != 0)
|
||||
return false;
|
||||
/* exact match or has '/' separator */
|
||||
if (sec_name[len] == '\0' || sec_name[len] == '/')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* SEC_SLOPPY_PFX definitions are allowed to be just prefix
|
||||
* matches, unless strict section name mode
|
||||
* (LIBBPF_STRICT_SEC_NAME) is enabled, in which case the
|
||||
* match has to be exact.
|
||||
*/
|
||||
if (allow_sloppy && str_has_pfx(sec_name, sec_def->sec))
|
||||
return true;
|
||||
|
||||
/* Definitions not marked SEC_SLOPPY_PFX (e.g.,
|
||||
* SEC("syscall")) are exact matches in both modes.
|
||||
*/
|
||||
return strcmp(sec_name, sec_def->sec) == 0;
|
||||
}
|
||||
|
||||
static const struct bpf_sec_def *find_sec_def(const char *sec_name)
|
||||
{
|
||||
const struct bpf_sec_def *sec_def;
|
||||
enum sec_def_flags sec_flags;
|
||||
int i, n = ARRAY_SIZE(section_defs), len;
|
||||
bool strict = libbpf_mode & LIBBPF_STRICT_SEC_NAME;
|
||||
int i, n;
|
||||
bool strict = libbpf_mode & LIBBPF_STRICT_SEC_NAME, allow_sloppy;
|
||||
|
||||
n = custom_sec_def_cnt;
|
||||
for (i = 0; i < n; i++) {
|
||||
sec_def = §ion_defs[i];
|
||||
sec_flags = sec_def->cookie;
|
||||
len = strlen(sec_def->sec);
|
||||
|
||||
/* "type/" always has to have proper SEC("type/extras") form */
|
||||
if (sec_def->sec[len - 1] == '/') {
|
||||
if (str_has_pfx(sec_name, sec_def->sec))
|
||||
return sec_def;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* "type+" means it can be either exact SEC("type") or
|
||||
* well-formed SEC("type/extras") with proper '/' separator
|
||||
*/
|
||||
if (sec_def->sec[len - 1] == '+') {
|
||||
len--;
|
||||
/* not even a prefix */
|
||||
if (strncmp(sec_name, sec_def->sec, len) != 0)
|
||||
continue;
|
||||
/* exact match or has '/' separator */
|
||||
if (sec_name[len] == '\0' || sec_name[len] == '/')
|
||||
return sec_def;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* SEC_SLOPPY_PFX definitions are allowed to be just prefix
|
||||
* matches, unless strict section name mode
|
||||
* (LIBBPF_STRICT_SEC_NAME) is enabled, in which case the
|
||||
* match has to be exact.
|
||||
*/
|
||||
if ((sec_flags & SEC_SLOPPY_PFX) && !strict) {
|
||||
if (str_has_pfx(sec_name, sec_def->sec))
|
||||
return sec_def;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Definitions not marked SEC_SLOPPY_PFX (e.g.,
|
||||
* SEC("syscall")) are exact matches in both modes.
|
||||
*/
|
||||
if (strcmp(sec_name, sec_def->sec) == 0)
|
||||
sec_def = &custom_sec_defs[i];
|
||||
if (sec_def_matches(sec_def, sec_name, false))
|
||||
return sec_def;
|
||||
}
|
||||
|
||||
n = ARRAY_SIZE(section_defs);
|
||||
for (i = 0; i < n; i++) {
|
||||
sec_def = §ion_defs[i];
|
||||
allow_sloppy = (sec_def->cookie & SEC_SLOPPY_PFX) && !strict;
|
||||
if (sec_def_matches(sec_def, sec_name, allow_sloppy))
|
||||
return sec_def;
|
||||
}
|
||||
|
||||
if (has_custom_fallback_def)
|
||||
return &custom_fallback_def;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define MAX_TYPE_NAME_SIZE 32
|
||||
|
||||
static char *libbpf_get_type_names(bool attach_type)
|
||||
{
|
||||
int i, len = ARRAY_SIZE(section_defs) * MAX_TYPE_NAME_SIZE;
|
||||
|
|
@ -8752,7 +8853,7 @@ static char *libbpf_get_type_names(bool attach_type)
|
|||
const struct bpf_sec_def *sec_def = §ion_defs[i];
|
||||
|
||||
if (attach_type) {
|
||||
if (sec_def->preload_fn != libbpf_preload_prog)
|
||||
if (sec_def->prog_prepare_load_fn != libbpf_prepare_prog_load)
|
||||
continue;
|
||||
|
||||
if (!(sec_def->cookie & SEC_ATTACHABLE))
|
||||
|
|
@ -9135,7 +9236,7 @@ int libbpf_attach_type_by_name(const char *name,
|
|||
return libbpf_err(-EINVAL);
|
||||
}
|
||||
|
||||
if (sec_def->preload_fn != libbpf_preload_prog)
|
||||
if (sec_def->prog_prepare_load_fn != libbpf_prepare_prog_load)
|
||||
return libbpf_err(-EINVAL);
|
||||
if (!(sec_def->cookie & SEC_ATTACHABLE))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
|
@ -10109,14 +10210,13 @@ struct bpf_link *bpf_program__attach_kprobe(const struct bpf_program *prog,
|
|||
return bpf_program__attach_kprobe_opts(prog, func_name, &opts);
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cookie)
|
||||
static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_kprobe_opts, opts);
|
||||
unsigned long offset = 0;
|
||||
struct bpf_link *link;
|
||||
const char *func_name;
|
||||
char *func;
|
||||
int n, err;
|
||||
int n;
|
||||
|
||||
opts.retprobe = str_has_pfx(prog->sec_name, "kretprobe/");
|
||||
if (opts.retprobe)
|
||||
|
|
@ -10126,21 +10226,19 @@ static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cooki
|
|||
|
||||
n = sscanf(func_name, "%m[a-zA-Z0-9_.]+%li", &func, &offset);
|
||||
if (n < 1) {
|
||||
err = -EINVAL;
|
||||
pr_warn("kprobe name is invalid: %s\n", func_name);
|
||||
return libbpf_err_ptr(err);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (opts.retprobe && offset != 0) {
|
||||
free(func);
|
||||
err = -EINVAL;
|
||||
pr_warn("kretprobes do not support offset specification\n");
|
||||
return libbpf_err_ptr(err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
opts.offset = offset;
|
||||
link = bpf_program__attach_kprobe_opts(prog, func, &opts);
|
||||
*link = bpf_program__attach_kprobe_opts(prog, func, &opts);
|
||||
free(func);
|
||||
return link;
|
||||
return libbpf_get_error(*link);
|
||||
}
|
||||
|
||||
static void gen_uprobe_legacy_event_name(char *buf, size_t buf_sz,
|
||||
|
|
@ -10395,14 +10493,13 @@ struct bpf_link *bpf_program__attach_tracepoint(const struct bpf_program *prog,
|
|||
return bpf_program__attach_tracepoint_opts(prog, tp_category, tp_name, NULL);
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie)
|
||||
static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
char *sec_name, *tp_cat, *tp_name;
|
||||
struct bpf_link *link;
|
||||
|
||||
sec_name = strdup(prog->sec_name);
|
||||
if (!sec_name)
|
||||
return libbpf_err_ptr(-ENOMEM);
|
||||
return -ENOMEM;
|
||||
|
||||
/* extract "tp/<category>/<name>" or "tracepoint/<category>/<name>" */
|
||||
if (str_has_pfx(prog->sec_name, "tp/"))
|
||||
|
|
@ -10412,14 +10509,14 @@ static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie)
|
|||
tp_name = strchr(tp_cat, '/');
|
||||
if (!tp_name) {
|
||||
free(sec_name);
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
*tp_name = '\0';
|
||||
tp_name++;
|
||||
|
||||
link = bpf_program__attach_tracepoint(prog, tp_cat, tp_name);
|
||||
*link = bpf_program__attach_tracepoint(prog, tp_cat, tp_name);
|
||||
free(sec_name);
|
||||
return link;
|
||||
return libbpf_get_error(*link);
|
||||
}
|
||||
|
||||
struct bpf_link *bpf_program__attach_raw_tracepoint(const struct bpf_program *prog,
|
||||
|
|
@ -10452,7 +10549,7 @@ struct bpf_link *bpf_program__attach_raw_tracepoint(const struct bpf_program *pr
|
|||
return link;
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cookie)
|
||||
static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
static const char *const prefixes[] = {
|
||||
"raw_tp/",
|
||||
|
|
@ -10472,10 +10569,11 @@ static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cooki
|
|||
if (!tp_name) {
|
||||
pr_warn("prog '%s': invalid section name '%s'\n",
|
||||
prog->name, prog->sec_name);
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return bpf_program__attach_raw_tracepoint(prog, tp_name);
|
||||
*link = bpf_program__attach_raw_tracepoint(prog, tp_name);
|
||||
return libbpf_get_error(link);
|
||||
}
|
||||
|
||||
/* Common logic for all BPF program types that attach to a btf_id */
|
||||
|
|
@ -10518,14 +10616,16 @@ struct bpf_link *bpf_program__attach_lsm(const struct bpf_program *prog)
|
|||
return bpf_program__attach_btf_id(prog);
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_trace(const struct bpf_program *prog, long cookie)
|
||||
static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
return bpf_program__attach_trace(prog);
|
||||
*link = bpf_program__attach_trace(prog);
|
||||
return libbpf_get_error(*link);
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_lsm(const struct bpf_program *prog, long cookie)
|
||||
static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
return bpf_program__attach_lsm(prog);
|
||||
*link = bpf_program__attach_lsm(prog);
|
||||
return libbpf_get_error(*link);
|
||||
}
|
||||
|
||||
static struct bpf_link *
|
||||
|
|
@ -10654,17 +10754,33 @@ bpf_program__attach_iter(const struct bpf_program *prog,
|
|||
return link;
|
||||
}
|
||||
|
||||
static struct bpf_link *attach_iter(const struct bpf_program *prog, long cookie)
|
||||
static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_link **link)
|
||||
{
|
||||
return bpf_program__attach_iter(prog, NULL);
|
||||
*link = bpf_program__attach_iter(prog, NULL);
|
||||
return libbpf_get_error(*link);
|
||||
}
|
||||
|
||||
struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
|
||||
{
|
||||
if (!prog->sec_def || !prog->sec_def->attach_fn)
|
||||
return libbpf_err_ptr(-ESRCH);
|
||||
struct bpf_link *link = NULL;
|
||||
int err;
|
||||
|
||||
return prog->sec_def->attach_fn(prog, prog->sec_def->cookie);
|
||||
if (!prog->sec_def || !prog->sec_def->prog_attach_fn)
|
||||
return libbpf_err_ptr(-EOPNOTSUPP);
|
||||
|
||||
err = prog->sec_def->prog_attach_fn(prog, prog->sec_def->cookie, &link);
|
||||
if (err)
|
||||
return libbpf_err_ptr(err);
|
||||
|
||||
/* When calling bpf_program__attach() explicitly, auto-attach support
|
||||
* is expected to work, so NULL returned link is considered an error.
|
||||
* This is different for skeleton's attach, see comment in
|
||||
* bpf_object__attach_skeleton().
|
||||
*/
|
||||
if (!link)
|
||||
return libbpf_err_ptr(-EOPNOTSUPP);
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
static int bpf_link__detach_struct_ops(struct bpf_link *link)
|
||||
|
|
@ -11805,16 +11921,30 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)
|
|||
continue;
|
||||
|
||||
/* auto-attaching not supported for this program */
|
||||
if (!prog->sec_def || !prog->sec_def->attach_fn)
|
||||
if (!prog->sec_def || !prog->sec_def->prog_attach_fn)
|
||||
continue;
|
||||
|
||||
*link = bpf_program__attach(prog);
|
||||
err = libbpf_get_error(*link);
|
||||
/* if user already set the link manually, don't attempt auto-attach */
|
||||
if (*link)
|
||||
continue;
|
||||
|
||||
err = prog->sec_def->prog_attach_fn(prog, prog->sec_def->cookie, link);
|
||||
if (err) {
|
||||
pr_warn("failed to auto-attach program '%s': %d\n",
|
||||
pr_warn("prog '%s': failed to auto-attach: %d\n",
|
||||
bpf_program__name(prog), err);
|
||||
return libbpf_err(err);
|
||||
}
|
||||
|
||||
/* It's possible that for some SEC() definitions auto-attach
|
||||
* is supported in some cases (e.g., if definition completely
|
||||
* specifies target information), but is not in other cases.
|
||||
* SEC("uprobe") is one such case. If user specified target
|
||||
* binary and function name, such BPF program can be
|
||||
* auto-attached. But if not, it shouldn't trigger skeleton's
|
||||
* attach to fail. It should just be skipped.
|
||||
* attach_fn signals such case with returning 0 (no error) and
|
||||
* setting link to NULL.
|
||||
*/
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
176
tools/testing/selftests/bpf/prog_tests/custom_sec_handlers.c
Normal file
176
tools/testing/selftests/bpf/prog_tests/custom_sec_handlers.c
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
// 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");
|
||||
}
|
||||
63
tools/testing/selftests/bpf/progs/test_custom_sec_handlers.c
Normal file
63
tools/testing/selftests/bpf/progs/test_custom_sec_handlers.c
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// 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";
|
||||
Loading…
Reference in New Issue
Block a user