Re: [PATCH bpf-next 2/3] libbpf: support custom SEC() handlers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, 5 Feb 2022, Andrii Nakryiko wrote:

> Allow registering and unregistering custom handlers for BPF program.
> This allows user applications and libraries to plug into libbpf's
> declarative SEC() definition handling logic. This allows to offload
> complex and intricate custom logic into external libraries, but still
> provide a great user experience.
> 
> One such example is USDT handling library, which has a lot of code and
> complexity which doesn't make sense to put into libbpf directly, but it
> would be really great for users to be able to specify BPF programs with
> something like SEC("usdt/<path-to-binary>:<usdt_provider>:<usdt_name>")
> and have correct BPF program type set (BPF_PROGRAM_TYPE_KPROBE, as it is
> uprobe) and even support BPF skeleton's auto-attach logic.
> 
> In some cases, it might be even good idea to override libbpf's default
> handling, like for SEC("perf_event") programs. With custom library, it's
> possible to extend logic to support specifying perf event specification
> right there in SEC() definition without burdening libbpf with lots of
> custom logic or extra library dependecies (e.g., libpfm4). With current
> patch it's possible to override libbpf's SEC("perf_event") handling and
> specify a completely custom ones.
> 
> Further, it's possible to specify a generic fallback handling for any
> SEC() that doesn't match any other custom or standard libbpf handlers.
> This allows to accommodate whatever legacy use cases there might be, if
> necessary.
> 
> See doc comments for libbpf_register_prog_handler() and
> libbpf_unregister_prog_handler() for detailed semantics.
> 
> Signed-off-by: Andrii Nakryiko <andrii@xxxxxxxxxx>
> ---
>  tools/lib/bpf/libbpf.c   | 201 +++++++++++++++++++++++++++++----------
>  tools/lib/bpf/libbpf.h   |  81 ++++++++++++++++
>  tools/lib/bpf/libbpf.map |   2 +
>  3 files changed, 232 insertions(+), 52 deletions(-)
> 
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 2902534def2c..d78a6365ba74 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -209,13 +209,6 @@ struct reloc_desc {
>  	};
>  };
>  
> -typedef int (*libbpf_prog_init_fn_t)(struct bpf_program *prog, long cookie);
> -typedef int (*libbpf_prog_preload_fn_t)(struct bpf_program *prog,
> -					struct bpf_prog_load_opts *opts, long cookie);
> -/* If auto-attach is not supported, callback should return 0 and set link to NULL */
> -typedef int (*libbpf_prog_attach_fn_t)(const struct bpf_program *prog, long cookie,
> -				       struct bpf_link **link);
> -
>  /* stored as sec_def->cookie for all libbpf-supported SEC()s */
>  enum sec_def_flags {
>  	SEC_NONE = 0,
> @@ -243,10 +236,11 @@ 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;
>  
>  	libbpf_prog_init_fn_t init_fn;
>  	libbpf_prog_preload_fn_t preload_fn;
> @@ -8582,7 +8576,7 @@ 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),					    \
> @@ -8675,61 +8669,164 @@ 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 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,
> +				 libbpf_prog_init_fn_t prog_init_fn,
> +				 libbpf_prog_preload_fn_t prog_preload_fn,
> +				 libbpf_prog_attach_fn_t prog_attach_fn,
> +				 long cookie,
> +				 const void *opts)
>  {
> -	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;
> +	struct bpf_sec_def *sec_def;
>  
> -	for (i = 0; i < n; i++) {
> -		sec_def = &section_defs[i];
> -		sec_flags = sec_def->cookie;
> -		len = strlen(sec_def->sec);
> +	if (opts)
> +		return libbpf_err(-EINVAL);
> +	if (last_custom_sec_def_handler_id == INT_MAX) /* prevent overflow */
> +		return libbpf_err(-E2BIG);
>  
> -		/* "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;
> -		}
> +	if (sec) {
> +		sec_def = libbpf_reallocarray(custom_sec_defs, custom_sec_def_cnt + 1,
> +					      sizeof(*sec_def));
> +		if (!sec_def)
> +			return libbpf_err(-ENOMEM);
>  
> -		/* "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;
> -		}
> +		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_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;
> -		}
> +		sec_def = &custom_fallback_def;
> +	}
>  
> -		/* 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->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 = cookie;
> +
> +	sec_def->init_fn = prog_init_fn;
> +	sec_def->preload_fn = prog_preload_fn;
> +	sec_def->attach_fn = prog_attach_fn;
> +
> +	sec_def->handler_id = ++last_custom_sec_def_handler_id;
> +
> +	if (sec)
> +		custom_sec_def_cnt++;
> +	else
> +		has_custom_fallback_def = true;
> +

should we try and deal with the (unlikely) case that multiple
fallback definitions are supplied, since only the first will
be used? i.e 

if (!sec && has_custom_fallback_def)
	return -EEXIST;

?

> +	return sec_def->handler_id;
> +}
> +
> +int libbpf_unregister_prog_handler(int handler_id)
> +{
> +	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--;

We're leaking a custom table entry each time we register/deregister.
We could libbpf_reallocarray() to trim here I think.

> +
> +	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;
> +	}
> +
> +	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;
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index c8d8daad212e..6e665c26dcc7 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -1328,6 +1328,87 @@ 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_init_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_preload_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);
> +
> +/**
> + * @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 prog_init_fn BPF program initialization callback (see libbpf_prog_init_fn_t)
> + * @param prog_preload_fn BPF program loading callback (see libbpf_prog_preload_fn_t)
> + * @param prog_attach_fn BPF program attach callback (see libbpf_prog_attach_fn_t)
> + * @param cookie User-provided cookie passed to each callback
> + * @param opts reserved for future extensibility, should be NULL
> + * @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).
> + */

Nicely documented!

> +LIBBPF_API int libbpf_register_prog_handler(const char *sec,
> +					    enum bpf_prog_type prog_type,
> +					    enum bpf_attach_type exp_attach_type,
> +					    libbpf_prog_init_fn_t prog_init_fn,
> +					    libbpf_prog_preload_fn_t prog_preload_fn,
> +					    libbpf_prog_attach_fn_t prog_attach_fn,

Naming nit: a prog_handler sounds less specific; would
"sec_handler" or "prog_sec_handler" be more descriptive perhaps?

Also, would it make sense to pass the functions in as options instead?
They can all be NULL potentially I think, and it's possible we'd
want additional future handlers too..

> +					    long cookie,
> +					    const void *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
> + */
> +LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);
> +
>  #ifdef __cplusplus
>  } /* extern "C" */
>  #endif
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index aef6253a90c8..4e75f06c1a00 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -438,4 +438,6 @@ LIBBPF_0.7.0 {
>  		libbpf_probe_bpf_map_type;
>  		libbpf_probe_bpf_prog_type;
>  		libbpf_set_memlock_rlim_max;
> +		libbpf_register_prog_handler;
> +		libbpf_unregister_prog_handler;
>  };
> -- 
> 2.30.2
> 
> 



[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux