Re: [PATCH bpf-next 1/3] libbpf: allow BPF program auto-attach handlers to bail out

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

 



On Sat, 5 Feb 2022, Andrii Nakryiko wrote:

> Allow some BPF program types to support auto-attach only in subste of
> cases. Currently, if some BPF program type specifies attach callback, it
> is assumed that during skeleton attach operation all such programs
> either successfully attach or entire skeleton attachment fails. If some
> program doesn't support auto-attachment from skeleton, such BPF program
> types shouldn't have attach callback specified.
>

This is a great feature! I've had cases where I had to
implement custom section-specific handling before, so this 
will make that process much easier!

> This is limiting for cases when, depending on how full the SEC("")
> definition is, there could either be enough details to support
> auto-attach or there might not be and user has to use some specific API
> to provide more details at runtime.
> 
> One specific example of such desired behavior might be SEC("uprobe"). If
> it's specified as just uprobe auto-attach isn't possible. But if it's
> SEC("uprobe/<some_binary>:<some_func>") then there are enough details to
> support auto-attach.

Would be good to describe the different handling for explicit
bpf_program__attach() (which fails when auto-attach is supported
but does not return a non-NULL link) vs bpf_object__attach_skeleton()
(which skips the NULL link case) here I think; it's all clarified in 
comments below but no harm to reiterate at the top-level I think. 

> 
> Another improvement to the way libbpf is handling SEC()s would be to not
> require providing dummy kernel function name for kprobe. Currently,
> SEC("kprobe/whatever") is necessary even if actual kernel function is
> determined by user at runtime and bpf_program__attach_kprobe() is used
> to specify it. With changes in this patch, it's possible to support both
> SEC("kprobe") and SEC("kprobe/<actual_kernel_function"), while only in
> the latter case auto-attach will be performed. In the former one, such
> kprobe will be skipped during skeleton attach operation.
> 
> Signed-off-by: Andrii Nakryiko <andrii@xxxxxxxxxx>

A few nits and suggestions for future, but this looks great!

Reviewed-by: Alan Maguire <alan.maguire@xxxxxxxxxx>

> ---
>  tools/lib/bpf/libbpf.c | 110 +++++++++++++++++++++++++----------------
>  1 file changed, 67 insertions(+), 43 deletions(-)
> 
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 904cdf83002b..2902534def2c 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -209,11 +209,12 @@ 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);
> +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 {
> @@ -247,9 +248,9 @@ struct bpf_sec_def {
>  	enum bpf_attach_type expected_attach_type;
>  	long cookie;
>  
> -	init_fn_t init_fn;
> -	preload_fn_t preload_fn;
> -	attach_fn_t attach_fn;
> +	libbpf_prog_init_fn_t init_fn;
> +	libbpf_prog_preload_fn_t preload_fn;
> +	libbpf_prog_attach_fn_t attach_fn;
>  };
>  
>  /*
> @@ -8589,12 +8590,12 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log
>  	__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);
>

One thought here - in the future it might be useful to export
these internal auto-attach functions.  The reason I suggest this
is some use-cases of auto-attach might involve pre-processing of
the section name, and once the required info is extracted the
auto-attach function could use the original auto-attach functionality.
That could be done separately to what you're doing here of course.

One concrete example of this: I had a BPF program which consisted
of BPF programs containing required attachments to top-level protocol 
module functions along with a set of optional attachments to
transport-specific module functions.  Since multiple transports were
possible, it was always possible that module A wouldn't be loaded
while module B was, or vice versa.  To deal with this, I used the
"o" prefix (optional) for the associated kprobe/kretprobe section 
definitions; an "okprobe" might not attach, but a "kprobe" should.

Using the mechanisms in this patch set, this could be easily
implemented by a custom auto-attach which looked for the "o"
then passed the rest of the section string into the default
auto-attach function for kprobes, handling attach errors for
optional sections while passing them through for required ones.
Tracers might find such pre-processing combined with the default
mechanisms useful too; we could even potentially implement
support for ":" separators this way too (convert instances
of ":" to "/" and then call default auto-attach)!
   
>  static const struct bpf_sec_def section_defs[] = {
>  	SEC_DEF("socket",		SOCKET_FILTER, 0, SEC_NONE | SEC_SLOPPY_PFX),
> @@ -10101,14 +10102,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)
> @@ -10118,21 +10118,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,
> @@ -10387,14 +10385,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/"))
> @@ -10404,14 +10401,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,
> @@ -10444,7 +10441,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/",
> @@ -10464,10 +10461,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 */
> @@ -10510,14 +10508,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 *
> @@ -10646,17 +10646,31 @@ 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)
>  {
> +	struct bpf_link *link;

might be no harm to initialize link to NULL; we could imagine
a user-supplied auto-attach function bailing early and not
remembering to set it explicitly.

> +	int err;
> +
>  	if (!prog->sec_def || !prog->sec_def->attach_fn)
> -		return libbpf_err_ptr(-ESRCH);
> +		return libbpf_err_ptr(-EOPNOTSUPP);
> +
> +	err = prog->sec_def->attach_fn(prog, prog->sec_def->cookie, &link);
> +	if (err)
> +		return libbpf_err_ptr(err);
> +
> +	/* auto-attach support is optional (see also comment in
> +	 * bpf_object__attach_skeleton()), but when explicitly expected by
> +	 * user it's an error if it's not */

nit: checkpatch wants the closing "*/" on the next line.
Also I think it would be good to clarify along the lines
of "when calling bpf_program__attach() explicitly, auto-attach
support is expected to work, and a NULL link is considered as
an error.  See comment in bpf_object__attach_skeleton() which
describes different handling of the 0 return value/NULL link
there."

> +	if (!link)
> +		return libbpf_err_ptr(-EOPNOTSUPP);
>  
> -	return prog->sec_def->attach_fn(prog, prog->sec_def->cookie);
> +	return link;
>  }
>  
>  static int bpf_link__detach_struct_ops(struct bpf_link *link)
> @@ -11800,13 +11814,23 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)
>  		if (!prog->sec_def || !prog->sec_def->attach_fn)
>  			continue;
>  
> -		*link = bpf_program__attach(prog);
> -		err = libbpf_get_error(*link);
> +		err = prog->sec_def->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;
> -- 
> 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