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
...@@ -201,12 +201,6 @@ struct reloc_desc { ...@@ -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 */ /* stored as sec_def->cookie for all libbpf-supported SEC()s */
enum sec_def_flags { enum sec_def_flags {
SEC_NONE = 0, SEC_NONE = 0,
...@@ -234,14 +228,15 @@ enum sec_def_flags { ...@@ -234,14 +228,15 @@ enum sec_def_flags {
}; };
struct bpf_sec_def { struct bpf_sec_def {
const char *sec; char *sec;
enum bpf_prog_type prog_type; enum bpf_prog_type prog_type;
enum bpf_attach_type expected_attach_type; enum bpf_attach_type expected_attach_type;
long cookie; long cookie;
int handler_id;
init_fn_t init_fn; libbpf_prog_setup_fn_t prog_setup_fn;
preload_fn_t preload_fn; libbpf_prog_prepare_load_fn_t prog_prepare_load_fn;
attach_fn_t attach_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 ...@@ -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, static int libbpf_find_attach_btf_id(struct bpf_program *prog, const char *attach_name,
int *btf_obj_fd, int *btf_type_id); int *btf_obj_fd, int *btf_type_id);
/* this is called as prog->sec_def->preload_fn for libbpf-supported sec_defs */ /* this is called as prog->sec_def->prog_prepare_load_fn for libbpf-supported sec_defs */
static int libbpf_preload_prog(struct bpf_program *prog, static int libbpf_prepare_prog_load(struct bpf_program *prog,
struct bpf_prog_load_opts *opts, long cookie) struct bpf_prog_load_opts *opts, long cookie)
{ {
enum sec_def_flags def = 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 ...@@ -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; load_attr.fd_array = obj->fd_array;
/* adjust load_attr if sec_def provides custom preload callback */ /* adjust load_attr if sec_def provides custom preload callback */
if (prog->sec_def && prog->sec_def->preload_fn) { if (prog->sec_def && prog->sec_def->prog_prepare_load_fn) {
err = prog->sec_def->preload_fn(prog, &load_attr, prog->sec_def->cookie); err = prog->sec_def->prog_prepare_load_fn(prog, &load_attr, prog->sec_def->cookie);
if (err < 0) { if (err < 0) {
pr_warn("prog '%s': failed to prepare load attributes: %d\n", pr_warn("prog '%s': failed to prepare load attributes: %d\n",
prog->name, err); prog->name, err);
...@@ -6971,8 +6966,8 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object ...@@ -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 /* sec_def can have custom callback which should be called
* after bpf_program is initialized to adjust its properties * after bpf_program is initialized to adjust its properties
*/ */
if (prog->sec_def->init_fn) { if (prog->sec_def->prog_setup_fn) {
err = prog->sec_def->init_fn(prog, prog->sec_def->cookie); err = prog->sec_def->prog_setup_fn(prog, prog->sec_def->cookie);
if (err < 0) { if (err < 0) {
pr_warn("prog '%s': failed to initialize: %d\n", pr_warn("prog '%s': failed to initialize: %d\n",
prog->name, err); prog->name, err);
...@@ -8589,20 +8584,20 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log ...@@ -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, ...) { \ #define SEC_DEF(sec_pfx, ptype, atype, flags, ...) { \
.sec = sec_pfx, \ .sec = (char *)sec_pfx, \
.prog_type = BPF_PROG_TYPE_##ptype, \ .prog_type = BPF_PROG_TYPE_##ptype, \
.expected_attach_type = atype, \ .expected_attach_type = atype, \
.cookie = (long)(flags), \ .cookie = (long)(flags), \
.preload_fn = libbpf_preload_prog, \ .prog_prepare_load_fn = libbpf_prepare_prog_load, \
__VA_ARGS__ \ __VA_ARGS__ \
} }
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);
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);
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 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);
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);
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);
static const struct bpf_sec_def section_defs[] = { static const struct bpf_sec_def section_defs[] = {
SEC_DEF("socket", SOCKET_FILTER, 0, SEC_NONE | SEC_SLOPPY_PFX), SEC_DEF("socket", SOCKET_FILTER, 0, SEC_NONE | SEC_SLOPPY_PFX),
...@@ -8682,61 +8677,167 @@ static const struct bpf_sec_def section_defs[] = { ...@@ -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), 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 const struct bpf_sec_def *find_sec_def(const char *sec_name) 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)
{ {
const struct bpf_sec_def *sec_def; 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;
for (i = 0; i < n; i++) { if (!OPTS_VALID(opts, libbpf_prog_handler_opts))
sec_def = &section_defs[i]; return libbpf_err(-EINVAL);
sec_flags = sec_def->cookie;
len = strlen(sec_def->sec);
/* "type/" always has to have proper SEC("type/extras") form */ if (last_custom_sec_def_handler_id == INT_MAX) /* prevent overflow */
if (sec_def->sec[len - 1] == '/') { return libbpf_err(-E2BIG);
if (str_has_pfx(sec_name, sec_def->sec))
return sec_def;
continue;
}
/* "type+" means it can be either exact SEC("type") or if (sec) {
* well-formed SEC("type/extras") with proper '/' separator sec_def = libbpf_reallocarray(custom_sec_defs, custom_sec_def_cnt + 1,
*/ sizeof(*sec_def));
if (sec_def->sec[len - 1] == '+') { if (!sec_def)
len--; return libbpf_err(-ENOMEM);
/* 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 custom_sec_defs = sec_def;
* matches, unless strict section name mode sec_def = &custom_sec_defs[custom_sec_def_cnt];
* (LIBBPF_STRICT_SEC_NAME) is enabled, in which case the } else {
* match has to be exact. if (has_custom_fallback_def)
*/ return libbpf_err(-EBUSY);
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_def = &custom_fallback_def;
* SEC("syscall")) are exact matches in both modes. }
*/
if (strcmp(sec_name, sec_def->sec) == 0) 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;
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 = &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 = &section_defs[i];
allow_sloppy = (sec_def->cookie & SEC_SLOPPY_PFX) && !strict;
if (sec_def_matches(sec_def, sec_name, allow_sloppy))
return sec_def; return sec_def;
} }
if (has_custom_fallback_def)
return &custom_fallback_def;
return NULL; return NULL;
} }
#define MAX_TYPE_NAME_SIZE 32
static char *libbpf_get_type_names(bool attach_type) static char *libbpf_get_type_names(bool attach_type)
{ {
int i, len = ARRAY_SIZE(section_defs) * MAX_TYPE_NAME_SIZE; int i, len = ARRAY_SIZE(section_defs) * MAX_TYPE_NAME_SIZE;
...@@ -8752,7 +8853,7 @@ static char *libbpf_get_type_names(bool attach_type) ...@@ -8752,7 +8853,7 @@ static char *libbpf_get_type_names(bool attach_type)
const struct bpf_sec_def *sec_def = &section_defs[i]; const struct bpf_sec_def *sec_def = &section_defs[i];
if (attach_type) { if (attach_type) {
if (sec_def->preload_fn != libbpf_preload_prog) if (sec_def->prog_prepare_load_fn != libbpf_prepare_prog_load)
continue; continue;
if (!(sec_def->cookie & SEC_ATTACHABLE)) if (!(sec_def->cookie & SEC_ATTACHABLE))
...@@ -9135,7 +9236,7 @@ int libbpf_attach_type_by_name(const char *name, ...@@ -9135,7 +9236,7 @@ int libbpf_attach_type_by_name(const char *name,
return libbpf_err(-EINVAL); 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); return libbpf_err(-EINVAL);
if (!(sec_def->cookie & SEC_ATTACHABLE)) if (!(sec_def->cookie & SEC_ATTACHABLE))
return libbpf_err(-EINVAL); return libbpf_err(-EINVAL);
...@@ -10109,14 +10210,13 @@ struct bpf_link *bpf_program__attach_kprobe(const struct bpf_program *prog, ...@@ -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); 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); DECLARE_LIBBPF_OPTS(bpf_kprobe_opts, opts);
unsigned long offset = 0; unsigned long offset = 0;
struct bpf_link *link;
const char *func_name; const char *func_name;
char *func; char *func;
int n, err; int n;
opts.retprobe = str_has_pfx(prog->sec_name, "kretprobe/"); opts.retprobe = str_has_pfx(prog->sec_name, "kretprobe/");
if (opts.retprobe) if (opts.retprobe)
...@@ -10126,21 +10226,19 @@ static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cooki ...@@ -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); n = sscanf(func_name, "%m[a-zA-Z0-9_.]+%li", &func, &offset);
if (n < 1) { if (n < 1) {
err = -EINVAL;
pr_warn("kprobe name is invalid: %s\n", func_name); pr_warn("kprobe name is invalid: %s\n", func_name);
return libbpf_err_ptr(err); return -EINVAL;
} }
if (opts.retprobe && offset != 0) { if (opts.retprobe && offset != 0) {
free(func); free(func);
err = -EINVAL;
pr_warn("kretprobes do not support offset specification\n"); pr_warn("kretprobes do not support offset specification\n");
return libbpf_err_ptr(err); return -EINVAL;
} }
opts.offset = offset; opts.offset = offset;
link = bpf_program__attach_kprobe_opts(prog, func, &opts); *link = bpf_program__attach_kprobe_opts(prog, func, &opts);
free(func); free(func);
return link; return libbpf_get_error(*link);
} }
static void gen_uprobe_legacy_event_name(char *buf, size_t buf_sz, 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, ...@@ -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); 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; char *sec_name, *tp_cat, *tp_name;
struct bpf_link *link;
sec_name = strdup(prog->sec_name); sec_name = strdup(prog->sec_name);
if (!sec_name) if (!sec_name)
return libbpf_err_ptr(-ENOMEM); return -ENOMEM;
/* extract "tp/<category>/<name>" or "tracepoint/<category>/<name>" */ /* extract "tp/<category>/<name>" or "tracepoint/<category>/<name>" */
if (str_has_pfx(prog->sec_name, "tp/")) 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) ...@@ -10412,14 +10509,14 @@ static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie)
tp_name = strchr(tp_cat, '/'); tp_name = strchr(tp_cat, '/');
if (!tp_name) { if (!tp_name) {
free(sec_name); free(sec_name);
return libbpf_err_ptr(-EINVAL); return -EINVAL;
} }
*tp_name = '\0'; *tp_name = '\0';
tp_name++; 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); free(sec_name);
return link; return libbpf_get_error(*link);
} }
struct bpf_link *bpf_program__attach_raw_tracepoint(const struct bpf_program *prog, 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 ...@@ -10452,7 +10549,7 @@ struct bpf_link *bpf_program__attach_raw_tracepoint(const struct bpf_program *pr
return link; 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[] = { static const char *const prefixes[] = {
"raw_tp/", "raw_tp/",
...@@ -10472,10 +10569,11 @@ static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cooki ...@@ -10472,10 +10569,11 @@ static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cooki
if (!tp_name) { if (!tp_name) {
pr_warn("prog '%s': invalid section name '%s'\n", pr_warn("prog '%s': invalid section name '%s'\n",
prog->name, prog->sec_name); 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 */ /* 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) ...@@ -10518,14 +10616,16 @@ struct bpf_link *bpf_program__attach_lsm(const struct bpf_program *prog)
return bpf_program__attach_btf_id(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 * static struct bpf_link *
...@@ -10654,17 +10754,33 @@ bpf_program__attach_iter(const struct bpf_program *prog, ...@@ -10654,17 +10754,33 @@ bpf_program__attach_iter(const struct bpf_program *prog,
return link; 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) struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
{ {
if (!prog->sec_def || !prog->sec_def->attach_fn) struct bpf_link *link = NULL;
return libbpf_err_ptr(-ESRCH); int err;
if (!prog->sec_def || !prog->sec_def->prog_attach_fn)
return libbpf_err_ptr(-EOPNOTSUPP);
return prog->sec_def->attach_fn(prog, prog->sec_def->cookie); 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) 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) ...@@ -11805,16 +11921,30 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)
continue; continue;
/* auto-attaching not supported for this program */ /* 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;
/* if user already set the link manually, don't attempt auto-attach */
if (*link)
continue; continue;
*link = bpf_program__attach(prog); err = prog->sec_def->prog_attach_fn(prog, prog->sec_def->cookie, link);
err = libbpf_get_error(*link);
if (err) { 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); bpf_program__name(prog), err);
return libbpf_err(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; return 0;
......
...@@ -1328,6 +1328,115 @@ LIBBPF_API int bpf_linker__add_file(struct bpf_linker *linker, ...@@ -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 int bpf_linker__finalize(struct bpf_linker *linker);
LIBBPF_API void bpf_linker__free(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 #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif
......
...@@ -439,3 +439,9 @@ LIBBPF_0.7.0 { ...@@ -439,3 +439,9 @@ LIBBPF_0.7.0 {
libbpf_probe_bpf_prog_type; libbpf_probe_bpf_prog_type;
libbpf_set_memlock_rlim_max; libbpf_set_memlock_rlim_max;
} LIBBPF_0.6.0; } 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 @@ ...@@ -4,6 +4,6 @@
#define __LIBBPF_VERSION_H #define __LIBBPF_VERSION_H
#define LIBBPF_MAJOR_VERSION 0 #define LIBBPF_MAJOR_VERSION 0
#define LIBBPF_MINOR_VERSION 7 #define LIBBPF_MINOR_VERSION 8
#endif /* __LIBBPF_VERSION_H */ #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