diff mbox series

[bpf-next,v1,7/8] libbpf: sync progs autoload with maps autocreate for struct_ops maps

Message ID 20240227204556.17524-8-eddyz87@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series libbpf: type suffixes and autocreate flag for struct_ops maps | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 8 this patch: 8
netdev/build_tools success Errors and warnings before: 1 this patch: 1
netdev/cc_maintainers warning 6 maintainers not CCed: jolsa@kernel.org john.fastabend@gmail.com kpsingh@kernel.org song@kernel.org sdf@google.com haoluo@google.com
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning WARNING: line length of 91 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18

Commit Message

Eduard Zingerman Feb. 27, 2024, 8:45 p.m. UTC
Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
state for referenced programs.

E.g. for the BPF code below:

    SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
    SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }

    SEC(".struct_ops.link")
    struct test_ops___v1 A = {
        .foo = (void *)foo
    };

    SEC(".struct_ops.link")
    struct test_ops___v2 B = {
        .foo = (void *)foo,
        .bar = (void *)bar,
    };

And the following libbpf API calls:

    bpf_map__set_autocreate(skel->maps.A, true);
    bpf_map__set_autocreate(skel->maps.B, false);

The autoload would be enabled for program 'foo' and disabled for
program 'bar'.

Do not apply such toggling if program autoload state is set by a call
to bpf_program__set_autoload().

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

Comments

Kui-Feng Lee Feb. 27, 2024, 10:55 p.m. UTC | #1
On 2/27/24 12:45, Eduard Zingerman wrote:
> Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
> state for referenced programs.
> 
> E.g. for the BPF code below:
> 
>      SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
>      SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
> 
>      SEC(".struct_ops.link")
>      struct test_ops___v1 A = {
>          .foo = (void *)foo
>      };
> 
>      SEC(".struct_ops.link")
>      struct test_ops___v2 B = {
>          .foo = (void *)foo,
>          .bar = (void *)bar,
>      };
> 
> And the following libbpf API calls:
> 
>      bpf_map__set_autocreate(skel->maps.A, true);
>      bpf_map__set_autocreate(skel->maps.B, false);
> 
> The autoload would be enabled for program 'foo' and disabled for
> program 'bar'.
> 
> Do not apply such toggling if program autoload state is set by a call
> to bpf_program__set_autoload().
> 
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>   tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
>   1 file changed, 34 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index b39d3f2898a1..1ea3046724f8 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -446,13 +446,18 @@ struct bpf_program {
>   	struct bpf_object *obj;
>   
>   	int fd;
> -	bool autoload;
> +	bool autoload:1;
> +	bool autoload_user_set:1;
>   	bool autoattach;
>   	bool sym_global;
>   	bool mark_btf_static;
>   	enum bpf_prog_type type;
>   	enum bpf_attach_type expected_attach_type;
>   	int exception_cb_idx;
> +	/* total number of struct_ops maps with autocreate == true
> +	 * that reference this program
> +	 */
> +	__u32 struct_ops_refs;
>   
>   	int prog_ifindex;
>   	__u32 attach_btf_obj_fd;
> @@ -4509,6 +4514,28 @@ static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info)
>   	return 0;
>   }
>   
> +/* Sync autoload and autocreate state between struct_ops map and
> + * referenced programs.
> + */
> +static void bpf_map__struct_ops_toggle_progs_autoload(struct bpf_map *map, bool autocreate)
> +{
> +	struct bpf_program *prog;
> +	int i;
> +
> +	for (i = 0; i < btf_vlen(map->st_ops->type); ++i) {
> +		prog = map->st_ops->progs[i];
> +
> +		if (!prog || prog->autoload_user_set)
> +			continue;
> +
> +		if (autocreate)
> +			prog->struct_ops_refs++;
> +		else
> +			prog->struct_ops_refs--;
> +		prog->autoload = prog->struct_ops_refs != 0;
> +	}
> +}
> +

This part is related to the other patch [1], which allows
a user to change the value of a function pointer field. The behavior of
autocreate and autoload may suprise a user if the user call
bpf_map__set_autocreate() after changing the value of a function pointer
field.

[1] 
https://lore.kernel.org/all/20240227010432.714127-1-thinker.li@gmail.com/


>   bool bpf_map__autocreate(const struct bpf_map *map)
>   {
>   	return map->autocreate;
> @@ -4519,6 +4546,9 @@ int bpf_map__set_autocreate(struct bpf_map *map, bool autocreate)
>   	if (map->obj->loaded)
>   		return libbpf_err(-EBUSY);
>   
> +	if (map->st_ops && map->autocreate != autocreate)
> +		bpf_map__struct_ops_toggle_progs_autoload(map, autocreate);
> +
>   	map->autocreate = autocreate;
>   	return 0;
>   }
> @@ -8801,6 +8831,7 @@ int bpf_program__set_autoload(struct bpf_program *prog, bool autoload)
>   		return libbpf_err(-EINVAL);
>   
>   	prog->autoload = autoload;
> +	prog->autoload_user_set = 1;
>   	return 0;
>   }
>   
> @@ -9428,6 +9459,8 @@ static int bpf_object__collect_st_ops_relos(struct bpf_object *obj,
>   			return -EINVAL;
>   		}
>   
> +		if (map->autocreate)
> +			prog->struct_ops_refs++;
>   		st_ops->progs[member_idx] = prog;
>   	}
>
Eduard Zingerman Feb. 27, 2024, 11:09 p.m. UTC | #2
On Tue, 2024-02-27 at 14:55 -0800, Kui-Feng Lee wrote:
[...]

> > @@ -4509,6 +4514,28 @@ static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info)
> >   	return 0;
> >   }
> >   
> > +/* Sync autoload and autocreate state between struct_ops map and
> > + * referenced programs.
> > + */
> > +static void bpf_map__struct_ops_toggle_progs_autoload(struct bpf_map *map, bool autocreate)
> > +{
> > +	struct bpf_program *prog;
> > +	int i;
> > +
> > +	for (i = 0; i < btf_vlen(map->st_ops->type); ++i) {
> > +		prog = map->st_ops->progs[i];
> > +
> > +		if (!prog || prog->autoload_user_set)
> > +			continue;
> > +
> > +		if (autocreate)
> > +			prog->struct_ops_refs++;
> > +		else
> > +			prog->struct_ops_refs--;
> > +		prog->autoload = prog->struct_ops_refs != 0;
> > +	}
> > +}
> > +
> 
> This part is related to the other patch [1], which allows
> a user to change the value of a function pointer field. The behavior of
> autocreate and autoload may suprise a user if the user call
> bpf_map__set_autocreate() after changing the value of a function pointer
> field.
> 
> [1] 
> https://lore.kernel.org/all/20240227010432.714127-1-thinker.li@gmail.com/

So, it appears that with shadow types users would have more or less
convenient way to disable / enable related BPF programs
(the references to programs are available, but reference counting
 would have to be implemented by user using some additional data
 structure, if needed).

I don't see a way to reconcile shadow types with this autoload/autocreate toggling
=> my last two patches would have to be dropped.

Wdyt?
Kui-Feng Lee Feb. 27, 2024, 11:16 p.m. UTC | #3
On 2/27/24 15:09, Eduard Zingerman wrote:
> On Tue, 2024-02-27 at 14:55 -0800, Kui-Feng Lee wrote:
> [...]
> 
>>> @@ -4509,6 +4514,28 @@ static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info)
>>>    	return 0;
>>>    }
>>>    
>>> +/* Sync autoload and autocreate state between struct_ops map and
>>> + * referenced programs.
>>> + */
>>> +static void bpf_map__struct_ops_toggle_progs_autoload(struct bpf_map *map, bool autocreate)
>>> +{
>>> +	struct bpf_program *prog;
>>> +	int i;
>>> +
>>> +	for (i = 0; i < btf_vlen(map->st_ops->type); ++i) {
>>> +		prog = map->st_ops->progs[i];
>>> +
>>> +		if (!prog || prog->autoload_user_set)
>>> +			continue;
>>> +
>>> +		if (autocreate)
>>> +			prog->struct_ops_refs++;
>>> +		else
>>> +			prog->struct_ops_refs--;
>>> +		prog->autoload = prog->struct_ops_refs != 0;
>>> +	}
>>> +}
>>> +
>>
>> This part is related to the other patch [1], which allows
>> a user to change the value of a function pointer field. The behavior of
>> autocreate and autoload may suprise a user if the user call
>> bpf_map__set_autocreate() after changing the value of a function pointer
>> field.
>>
>> [1]
>> https://lore.kernel.org/all/20240227010432.714127-1-thinker.li@gmail.com/
> 
> So, it appears that with shadow types users would have more or less
> convenient way to disable / enable related BPF programs
> (the references to programs are available, but reference counting
>   would have to be implemented by user using some additional data
>   structure, if needed).
> 
> I don't see a way to reconcile shadow types with this autoload/autocreate toggling
> => my last two patches would have to be dropped.

How about to update autoload according to the value of autocreate of
maps before loading the programs? For example, update autoload in
bpf_map__init_kern_struct_ops()?

> 
> Wdyt?
Eduard Zingerman Feb. 27, 2024, 11:30 p.m. UTC | #4
On Tue, 2024-02-27 at 15:16 -0800, Kui-Feng Lee wrote:
[...]

> > So, it appears that with shadow types users would have more or less
> > convenient way to disable / enable related BPF programs
> > (the references to programs are available, but reference counting
> >   would have to be implemented by user using some additional data
> >   structure, if needed).
> > 
> > I don't see a way to reconcile shadow types with this autoload/autocreate toggling
> > => my last two patches would have to be dropped.
> 
> How about to update autoload according to the value of autocreate of
> maps before loading the programs? For example, update autoload in
> bpf_map__init_kern_struct_ops()?

This can be done, but it would have to be a separate pass:
first scanning all maps and setting up reference counters for programs,
then scanning all programs and disabling those unused.
I can do that in v2, thank you for the suggestion.

Still, this overlaps a bit with shadow types.
Do you have an idea if such auto-toggling would be helpful from libbpf
users point of view?
Kui-Feng Lee Feb. 27, 2024, 11:40 p.m. UTC | #5
On 2/27/24 15:30, Eduard Zingerman wrote:
> On Tue, 2024-02-27 at 15:16 -0800, Kui-Feng Lee wrote:
> [...]
> 
>>> So, it appears that with shadow types users would have more or less
>>> convenient way to disable / enable related BPF programs
>>> (the references to programs are available, but reference counting
>>>    would have to be implemented by user using some additional data
>>>    structure, if needed).
>>>
>>> I don't see a way to reconcile shadow types with this autoload/autocreate toggling
>>> => my last two patches would have to be dropped.
>>
>> How about to update autoload according to the value of autocreate of
>> maps before loading the programs? For example, update autoload in
>> bpf_map__init_kern_struct_ops()?
> 
> This can be done, but it would have to be a separate pass:
> first scanning all maps and setting up reference counters for programs,
> then scanning all programs and disabling those unused.
> I can do that in v2, thank you for the suggestion.
> 
> Still, this overlaps a bit with shadow types.
> Do you have an idea if such auto-toggling would be helpful from libbpf
> users point of view?

For me, it is useful. For minor adjustments, shadow types is easier and
simpler; for example, change a flag or a function.  But, the features
presented here are useful when a user want to switch between several
very different configurations. They may not want to change a large
number of fields.
Eduard Zingerman Feb. 27, 2024, 11:43 p.m. UTC | #6
On Tue, 2024-02-27 at 15:40 -0800, Kui-Feng Lee wrote:
[...]

> For me, it is useful. For minor adjustments, shadow types is easier and
> simpler; for example, change a flag or a function.  But, the features
> presented here are useful when a user want to switch between several
> very different configurations. They may not want to change a large
> number of fields.

Great, thank you for reviewing the patch-set.
I'll wait a bit for additional comments and post v2 tomorrow.
Kui-Feng Lee Feb. 28, 2024, 12:12 a.m. UTC | #7
On 2/27/24 15:30, Eduard Zingerman wrote:
> On Tue, 2024-02-27 at 15:16 -0800, Kui-Feng Lee wrote:
> [...]
> 
>>> So, it appears that with shadow types users would have more or less
>>> convenient way to disable / enable related BPF programs
>>> (the references to programs are available, but reference counting
>>>    would have to be implemented by user using some additional data
>>>    structure, if needed).
>>>
>>> I don't see a way to reconcile shadow types with this autoload/autocreate toggling
>>> => my last two patches would have to be dropped.
>>
>> How about to update autoload according to the value of autocreate of
>> maps before loading the programs? For example, update autoload in
>> bpf_map__init_kern_struct_ops()?
> 
> This can be done, but it would have to be a separate pass:
> first scanning all maps and setting up reference counters for programs,
> then scanning all programs and disabling those unused.
> I can do that in v2, thank you for the suggestion.
> 

It only has to scan once with an additional flag.
The value of the autoload of a prog should be
true if its autoload_user_set is false and autocreate of any one of
struct_ops maps pointing to the prog is true.

Let's say the flag is autoload_autocreate.
In bpf_map__init_kern_struct_ops(), it has to check
prog->autoload_user_set, and do prog->autoload |= map->autocreate if
prog->autoload_user_set is false and autoload_autocreate is true. Do 
prog->autoload = map->autocreate if autoload_autocreate is false I think 
it is enough, right?

if (!prog->autoload_user_set) {
     if (!prog->autoload_autocreate)
         prog->autoload = map->autocreate;
     else
         prog->autoload |= map->autocreate;
     prog->autoload_autocreate = true;
}
Eduard Zingerman Feb. 28, 2024, 12:50 a.m. UTC | #8
On Tue, 2024-02-27 at 16:12 -0800, Kui-Feng Lee wrote:
[...]

> It only has to scan once with an additional flag.
> The value of the autoload of a prog should be
> true if its autoload_user_set is false and autocreate of any one of
> struct_ops maps pointing to the prog is true.
> 
> Let's say the flag is autoload_autocreate.
> In bpf_map__init_kern_struct_ops(), it has to check
> prog->autoload_user_set, and do prog->autoload |= map->autocreate if
> prog->autoload_user_set is false and autoload_autocreate is true. Do 
> prog->autoload = map->autocreate if autoload_autocreate is false I think 
> it is enough, right?
> 
> if (!prog->autoload_user_set) {
>      if (!prog->autoload_autocreate)
>          prog->autoload = map->autocreate;
>      else
>          prog->autoload |= map->autocreate;
>      prog->autoload_autocreate = true;
> }

Since the full thing is moved to load phase I was hoping to make do
w/o changes to struct bpf_program. To have it more contained.
E.g. the code below apperas to work (needs more testing):

--- 8< -------------------------------------

static int bpf_object__adjust_struct_ops_autoload(struct bpf_object *obj)
{
	struct bpf_program *prog;
	struct bpf_map *map;
	int i, j, k, vlen;
	struct {
		__u8 autoload:1;
		/* only change autoload for programs that are
		 * referenced from some struct_ops maps
		 */
		__u8 used:1;
	} *refs;

	refs = calloc(obj->nr_programs, sizeof(refs[0]));
	if (!refs)
		return -ENOMEM;

	for (i = 0; i < obj->nr_maps; i++) {
		map = &obj->maps[i];
		if (!bpf_map__is_struct_ops(map))
			continue;

		vlen = btf_vlen(map->st_ops->type);
		for (j = 0; j < vlen; ++j) {
			prog = map->st_ops->progs[j];
			if (!prog)
				continue;

			k = prog - obj->programs;
			refs[k].used = true;
			refs[k].autoload |= map->autocreate;
		}
	}

	for (i = 0; i < obj->nr_programs; ++i) {
		prog = &obj->programs[i];
		if (prog->autoload_user_set || !refs[i].used)
			continue;

		prog->autoload = refs[i].autoload;
	}

	free(refs);
	return 0;
}

------------------------------------- >8 ---
Martin KaFai Lau Feb. 28, 2024, 2:10 a.m. UTC | #9
On 2/27/24 12:45 PM, Eduard Zingerman wrote:
> Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
> state for referenced programs.
> 
> E.g. for the BPF code below:
> 
>      SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
>      SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
> 
>      SEC(".struct_ops.link")
>      struct test_ops___v1 A = {
>          .foo = (void *)foo
>      };
> 
>      SEC(".struct_ops.link")
>      struct test_ops___v2 B = {
>          .foo = (void *)foo,
>          .bar = (void *)bar,
>      };
> 
> And the following libbpf API calls:
> 
>      bpf_map__set_autocreate(skel->maps.A, true);
>      bpf_map__set_autocreate(skel->maps.B, false);
> 
> The autoload would be enabled for program 'foo' and disabled for
> program 'bar'.
> 
> Do not apply such toggling if program autoload state is set by a call
> to bpf_program__set_autoload().
> 
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>   tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
>   1 file changed, 34 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index b39d3f2898a1..1ea3046724f8 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -446,13 +446,18 @@ struct bpf_program {
>   	struct bpf_object *obj;
>   
>   	int fd;
> -	bool autoload;
> +	bool autoload:1;
> +	bool autoload_user_set:1;
>   	bool autoattach;
>   	bool sym_global;
>   	bool mark_btf_static;
>   	enum bpf_prog_type type;
>   	enum bpf_attach_type expected_attach_type;
>   	int exception_cb_idx;
> +	/* total number of struct_ops maps with autocreate == true
> +	 * that reference this program
> +	 */
> +	__u32 struct_ops_refs;

Instead of adding struct_ops_refs and autoload_user_set,

for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking 
prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at 
load time and is only set if it is used by at least one autocreate map, if I 
read patch 2 & 3 correctly.

Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used 
by one struct_ops map with autocreate == true.

If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be 
loaded even the autoload is set. If bpf prog is used in a struct_ops map and its 
autoload is set to false, the struct_ops map will be in broken state. Thus, 
prog->autoload does not fit very well with struct_ops prog and may as well 
depend on whether the struct_ops prog is used by a struct_ops map alone?
Eduard Zingerman Feb. 28, 2024, 12:36 p.m. UTC | #10
On Tue, 2024-02-27 at 18:10 -0800, Martin KaFai Lau wrote:
[...]

> Instead of adding struct_ops_refs and autoload_user_set,
> 
> for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking 
> prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at 
> load time and is only set if it is used by at least one autocreate map, if I 
> read patch 2 & 3 correctly.
> 
> Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used 
> by one struct_ops map with autocreate == true.
> 
> If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be 
> loaded even the autoload is set. If bpf prog is used in a struct_ops map and its 
> autoload is set to false, the struct_ops map will be in broken state. Thus, 
> prog->autoload does not fit very well with struct_ops prog and may as well 
> depend on whether the struct_ops prog is used by a struct_ops map alone?

This makes sense.
The drawback is that introspection capability to query which programs
would be loaded is lost, maybe that is not a big deal.
It could be put back by adding an ugly loop iterating over all maps in
bpf_program__autoload() for struct_ops programs.

I think I'll post v2 with changes you suggest and see what others have to say.
Andrii Nakryiko Feb. 28, 2024, 11:55 p.m. UTC | #11
On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> On 2/27/24 12:45 PM, Eduard Zingerman wrote:
> > Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
> > state for referenced programs.
> >
> > E.g. for the BPF code below:
> >
> >      SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
> >      SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
> >
> >      SEC(".struct_ops.link")
> >      struct test_ops___v1 A = {
> >          .foo = (void *)foo
> >      };
> >
> >      SEC(".struct_ops.link")
> >      struct test_ops___v2 B = {
> >          .foo = (void *)foo,
> >          .bar = (void *)bar,
> >      };
> >
> > And the following libbpf API calls:
> >
> >      bpf_map__set_autocreate(skel->maps.A, true);
> >      bpf_map__set_autocreate(skel->maps.B, false);
> >
> > The autoload would be enabled for program 'foo' and disabled for
> > program 'bar'.
> >
> > Do not apply such toggling if program autoload state is set by a call
> > to bpf_program__set_autoload().
> >
> > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> > ---
> >   tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
> >   1 file changed, 34 insertions(+), 1 deletion(-)
> >
> > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > index b39d3f2898a1..1ea3046724f8 100644
> > --- a/tools/lib/bpf/libbpf.c
> > +++ b/tools/lib/bpf/libbpf.c
> > @@ -446,13 +446,18 @@ struct bpf_program {
> >       struct bpf_object *obj;
> >
> >       int fd;
> > -     bool autoload;
> > +     bool autoload:1;
> > +     bool autoload_user_set:1;
> >       bool autoattach;
> >       bool sym_global;
> >       bool mark_btf_static;
> >       enum bpf_prog_type type;
> >       enum bpf_attach_type expected_attach_type;
> >       int exception_cb_idx;
> > +     /* total number of struct_ops maps with autocreate == true
> > +      * that reference this program
> > +      */
> > +     __u32 struct_ops_refs;
>
> Instead of adding struct_ops_refs and autoload_user_set,
>
> for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
> prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
> load time and is only set if it is used by at least one autocreate map, if I
> read patch 2 & 3 correctly.
>
> Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
> by one struct_ops map with autocreate == true.
>
> If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
> loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
> autoload is set to false, the struct_ops map will be in broken state. Thus,

We can easily detect this condition and report meaningful error.

> prog->autoload does not fit very well with struct_ops prog and may as well
> depend on whether the struct_ops prog is used by a struct_ops map alone?

I think it's probably fine from a usability standpoint to disable
loading the BPF program if its struct_ops map was explicitly set to
not auto-create. It's a bit of deviation from other program types, but
in practice this logic will make it easier for users.

One question I have, though, is whether we should report as an error a
stand-alone struct_ops BPF program that is not used from any
struct_ops map? Or should we load it nevertheless? Or should we
silently not load it?

I feel like silently not loading is the worst behavior here. So either
loading it anyway or reporting an error would be my preference,
probably.
Eduard Zingerman Feb. 29, 2024, 12:04 a.m. UTC | #12
On Wed, 2024-02-28 at 15:55 -0800, Andrii Nakryiko wrote:
> On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:

[...]

> > Instead of adding struct_ops_refs and autoload_user_set,
> > 
> > for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
> > prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
> > load time and is only set if it is used by at least one autocreate map, if I
> > read patch 2 & 3 correctly.
> > 
> > Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
> > by one struct_ops map with autocreate == true.
> > 
> > If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
> > loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
> > autoload is set to false, the struct_ops map will be in broken state. Thus,
> 
> We can easily detect this condition and report meaningful error.
> 
> > prog->autoload does not fit very well with struct_ops prog and may as well
> > depend on whether the struct_ops prog is used by a struct_ops map alone?
> 
> I think it's probably fine from a usability standpoint to disable
> loading the BPF program if its struct_ops map was explicitly set to
> not auto-create. It's a bit of deviation from other program types, but
> in practice this logic will make it easier for users.
> 
> One question I have, though, is whether we should report as an error a
> stand-alone struct_ops BPF program that is not used from any
> struct_ops map? Or should we load it nevertheless? Or should we
> silently not load it?
> 
> I feel like silently not loading is the worst behavior here. So either
> loading it anyway or reporting an error would be my preference,
> probably.

The following properties of the struct_ops program are set based on
the corresponding struct_ops map:
- attach_btf_id - BTF id of the kernel struct_ops type;
- expected_attach_type - member index of function pointer inside
  the kernel type.

No corresponding map means above fields are not set,
means program fails to load with error report.

So I think it is fine to try loading such programs w/o any additional
processing.
Andrii Nakryiko Feb. 29, 2024, 12:14 a.m. UTC | #13
On Wed, Feb 28, 2024 at 4:04 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Wed, 2024-02-28 at 15:55 -0800, Andrii Nakryiko wrote:
> > On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> [...]
>
> > > Instead of adding struct_ops_refs and autoload_user_set,
> > >
> > > for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
> > > prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
> > > load time and is only set if it is used by at least one autocreate map, if I
> > > read patch 2 & 3 correctly.
> > >
> > > Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
> > > by one struct_ops map with autocreate == true.
> > >
> > > If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
> > > loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
> > > autoload is set to false, the struct_ops map will be in broken state. Thus,
> >
> > We can easily detect this condition and report meaningful error.
> >
> > > prog->autoload does not fit very well with struct_ops prog and may as well
> > > depend on whether the struct_ops prog is used by a struct_ops map alone?
> >
> > I think it's probably fine from a usability standpoint to disable
> > loading the BPF program if its struct_ops map was explicitly set to
> > not auto-create. It's a bit of deviation from other program types, but
> > in practice this logic will make it easier for users.
> >
> > One question I have, though, is whether we should report as an error a
> > stand-alone struct_ops BPF program that is not used from any
> > struct_ops map? Or should we load it nevertheless? Or should we
> > silently not load it?
> >
> > I feel like silently not loading is the worst behavior here. So either
> > loading it anyway or reporting an error would be my preference,
> > probably.
>
> The following properties of the struct_ops program are set based on
> the corresponding struct_ops map:
> - attach_btf_id - BTF id of the kernel struct_ops type;
> - expected_attach_type - member index of function pointer inside
>   the kernel type.
>
> No corresponding map means above fields are not set,
> means program fails to load with error report.
>
> So I think it is fine to try loading such programs w/o any additional
> processing.

But Martin is proposing to *not load* programs if their attach_btf_id
is not set. Which is why I'm asking if we should distinguish
situations where a BPF program is stand-alone (never was referenced
from struct_ops map) vs auto-disabling it because struct_ops map that
it was referenced from was explicitly disabled.
Martin KaFai Lau Feb. 29, 2024, 12:25 a.m. UTC | #14
On 2/28/24 3:55 PM, Andrii Nakryiko wrote:
> On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>>
>> On 2/27/24 12:45 PM, Eduard Zingerman wrote:
>>> Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
>>> state for referenced programs.
>>>
>>> E.g. for the BPF code below:
>>>
>>>       SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
>>>       SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
>>>
>>>       SEC(".struct_ops.link")
>>>       struct test_ops___v1 A = {
>>>           .foo = (void *)foo
>>>       };
>>>
>>>       SEC(".struct_ops.link")
>>>       struct test_ops___v2 B = {
>>>           .foo = (void *)foo,
>>>           .bar = (void *)bar,
>>>       };
>>>
>>> And the following libbpf API calls:
>>>
>>>       bpf_map__set_autocreate(skel->maps.A, true);
>>>       bpf_map__set_autocreate(skel->maps.B, false);
>>>
>>> The autoload would be enabled for program 'foo' and disabled for
>>> program 'bar'.
>>>
>>> Do not apply such toggling if program autoload state is set by a call
>>> to bpf_program__set_autoload().
>>>
>>> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
>>> ---
>>>    tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
>>>    1 file changed, 34 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>>> index b39d3f2898a1..1ea3046724f8 100644
>>> --- a/tools/lib/bpf/libbpf.c
>>> +++ b/tools/lib/bpf/libbpf.c
>>> @@ -446,13 +446,18 @@ struct bpf_program {
>>>        struct bpf_object *obj;
>>>
>>>        int fd;
>>> -     bool autoload;
>>> +     bool autoload:1;
>>> +     bool autoload_user_set:1;
>>>        bool autoattach;
>>>        bool sym_global;
>>>        bool mark_btf_static;
>>>        enum bpf_prog_type type;
>>>        enum bpf_attach_type expected_attach_type;
>>>        int exception_cb_idx;
>>> +     /* total number of struct_ops maps with autocreate == true
>>> +      * that reference this program
>>> +      */
>>> +     __u32 struct_ops_refs;
>>
>> Instead of adding struct_ops_refs and autoload_user_set,
>>
>> for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
>> prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
>> load time and is only set if it is used by at least one autocreate map, if I
>> read patch 2 & 3 correctly.
>>
>> Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
>> by one struct_ops map with autocreate == true.
>>
>> If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
>> loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
>> autoload is set to false, the struct_ops map will be in broken state. Thus,
> 
> We can easily detect this condition and report meaningful error.
> 
>> prog->autoload does not fit very well with struct_ops prog and may as well
>> depend on whether the struct_ops prog is used by a struct_ops map alone?
> 
> I think it's probably fine from a usability standpoint to disable
> loading the BPF program if its struct_ops map was explicitly set to
> not auto-create. It's a bit of deviation from other program types, but
> in practice this logic will make it easier for users.
> 
> One question I have, though, is whether we should report as an error a
> stand-alone struct_ops BPF program that is not used from any
> struct_ops map? Or should we load it nevertheless? Or should we
> silently not load it?

I think the libbpf could report an error if the prog is not used in any 
struct_ops map at the source code level, not sure if it is useful.

However, it probably should not report error if that struct_ops map (where the 
prog is resided) does not have autocreate set to true.

If a BPF program is not used in any struct_ops map, it cannot be loaded anyway 
because the prog->attach_btf_id is not set. If libbpf tries to load the prog, 
the kernel will reject it also. I think it may be a question on whether it is 
the user intention of not loading the prog if the prog is not used in any 
struct_ops map. I tend to think it is the user intention of not loading it in 
this case.

SEC("struct_ops/test1")
int BPF_PROG(test1) { ... }

SEC("struct_ops/test2")
int BPF_PROG(test2) { ... }

SEC("?.struct_ops.link") struct some_ops___v1 a = { .test1 = test1 }
SEC("?.struct_ops.link") struct some_ops___v2 b = { .test1 = test1,
						   .test2 = test2, }

In the above, the userspace may try to load with a newer some_ops___v2 first, 
failed and then try a lower version some_ops___v1 and then succeeded. The test2 
prog will not be used and not expected to be loaded.

> 
> I feel like silently not loading is the worst behavior here. So either
> loading it anyway or reporting an error would be my preference,
> probably.
Andrii Nakryiko Feb. 29, 2024, 12:30 a.m. UTC | #15
On Wed, Feb 28, 2024 at 4:25 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> On 2/28/24 3:55 PM, Andrii Nakryiko wrote:
> > On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
> >>
> >> On 2/27/24 12:45 PM, Eduard Zingerman wrote:
> >>> Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
> >>> state for referenced programs.
> >>>
> >>> E.g. for the BPF code below:
> >>>
> >>>       SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
> >>>       SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
> >>>
> >>>       SEC(".struct_ops.link")
> >>>       struct test_ops___v1 A = {
> >>>           .foo = (void *)foo
> >>>       };
> >>>
> >>>       SEC(".struct_ops.link")
> >>>       struct test_ops___v2 B = {
> >>>           .foo = (void *)foo,
> >>>           .bar = (void *)bar,
> >>>       };
> >>>
> >>> And the following libbpf API calls:
> >>>
> >>>       bpf_map__set_autocreate(skel->maps.A, true);
> >>>       bpf_map__set_autocreate(skel->maps.B, false);
> >>>
> >>> The autoload would be enabled for program 'foo' and disabled for
> >>> program 'bar'.
> >>>
> >>> Do not apply such toggling if program autoload state is set by a call
> >>> to bpf_program__set_autoload().
> >>>
> >>> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> >>> ---
> >>>    tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
> >>>    1 file changed, 34 insertions(+), 1 deletion(-)
> >>>
> >>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> >>> index b39d3f2898a1..1ea3046724f8 100644
> >>> --- a/tools/lib/bpf/libbpf.c
> >>> +++ b/tools/lib/bpf/libbpf.c
> >>> @@ -446,13 +446,18 @@ struct bpf_program {
> >>>        struct bpf_object *obj;
> >>>
> >>>        int fd;
> >>> -     bool autoload;
> >>> +     bool autoload:1;
> >>> +     bool autoload_user_set:1;
> >>>        bool autoattach;
> >>>        bool sym_global;
> >>>        bool mark_btf_static;
> >>>        enum bpf_prog_type type;
> >>>        enum bpf_attach_type expected_attach_type;
> >>>        int exception_cb_idx;
> >>> +     /* total number of struct_ops maps with autocreate == true
> >>> +      * that reference this program
> >>> +      */
> >>> +     __u32 struct_ops_refs;
> >>
> >> Instead of adding struct_ops_refs and autoload_user_set,
> >>
> >> for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
> >> prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
> >> load time and is only set if it is used by at least one autocreate map, if I
> >> read patch 2 & 3 correctly.
> >>
> >> Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
> >> by one struct_ops map with autocreate == true.
> >>
> >> If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
> >> loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
> >> autoload is set to false, the struct_ops map will be in broken state. Thus,
> >
> > We can easily detect this condition and report meaningful error.
> >
> >> prog->autoload does not fit very well with struct_ops prog and may as well
> >> depend on whether the struct_ops prog is used by a struct_ops map alone?
> >
> > I think it's probably fine from a usability standpoint to disable
> > loading the BPF program if its struct_ops map was explicitly set to
> > not auto-create. It's a bit of deviation from other program types, but
> > in practice this logic will make it easier for users.
> >
> > One question I have, though, is whether we should report as an error a
> > stand-alone struct_ops BPF program that is not used from any
> > struct_ops map? Or should we load it nevertheless? Or should we
> > silently not load it?
>
> I think the libbpf could report an error if the prog is not used in any
> struct_ops map at the source code level, not sure if it is useful.
>
> However, it probably should not report error if that struct_ops map (where the
> prog is resided) does not have autocreate set to true.
>
> If a BPF program is not used in any struct_ops map, it cannot be loaded anyway
> because the prog->attach_btf_id is not set. If libbpf tries to load the prog,
> the kernel will reject it also. I think it may be a question on whether it is
> the user intention of not loading the prog if the prog is not used in any
> struct_ops map. I tend to think it is the user intention of not loading it in
> this case.
>
> SEC("struct_ops/test1")
> int BPF_PROG(test1) { ... }
>
> SEC("struct_ops/test2")
> int BPF_PROG(test2) { ... }
>
> SEC("?.struct_ops.link") struct some_ops___v1 a = { .test1 = test1 }
> SEC("?.struct_ops.link") struct some_ops___v2 b = { .test1 = test1,
>                                                    .test2 = test2, }
>
> In the above, the userspace may try to load with a newer some_ops___v2 first,
> failed and then try a lower version some_ops___v1 and then succeeded. The test2
> prog will not be used and not expected to be loaded.
>

Yes, it's all sane in the above example. But imagine a stand-alone
struct_ops program with no SEC(".struct_ops") at all:


SEC("struct_ops/test1")
int BPF_PROG(test1) { ... }

/* nothing else */

Currently this will fail, right?

And with your proposal it will succeed without actually even
attempting to load the BPF program. Or am I misunderstanding?


> >
> > I feel like silently not loading is the worst behavior here. So either
> > loading it anyway or reporting an error would be my preference,
> > probably.
>
Martin KaFai Lau Feb. 29, 2024, 12:37 a.m. UTC | #16
On 2/28/24 4:30 PM, Andrii Nakryiko wrote:
> On Wed, Feb 28, 2024 at 4:25 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>>
>> On 2/28/24 3:55 PM, Andrii Nakryiko wrote:
>>> On Tue, Feb 27, 2024 at 6:11 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>>>>
>>>> On 2/27/24 12:45 PM, Eduard Zingerman wrote:
>>>>> Make bpf_map__set_autocreate() for struct_ops maps toggle autoload
>>>>> state for referenced programs.
>>>>>
>>>>> E.g. for the BPF code below:
>>>>>
>>>>>        SEC("struct_ops/test_1") int BPF_PROG(foo) { ... }
>>>>>        SEC("struct_ops/test_2") int BPF_PROG(bar) { ... }
>>>>>
>>>>>        SEC(".struct_ops.link")
>>>>>        struct test_ops___v1 A = {
>>>>>            .foo = (void *)foo
>>>>>        };
>>>>>
>>>>>        SEC(".struct_ops.link")
>>>>>        struct test_ops___v2 B = {
>>>>>            .foo = (void *)foo,
>>>>>            .bar = (void *)bar,
>>>>>        };
>>>>>
>>>>> And the following libbpf API calls:
>>>>>
>>>>>        bpf_map__set_autocreate(skel->maps.A, true);
>>>>>        bpf_map__set_autocreate(skel->maps.B, false);
>>>>>
>>>>> The autoload would be enabled for program 'foo' and disabled for
>>>>> program 'bar'.
>>>>>
>>>>> Do not apply such toggling if program autoload state is set by a call
>>>>> to bpf_program__set_autoload().
>>>>>
>>>>> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
>>>>> ---
>>>>>     tools/lib/bpf/libbpf.c | 35 ++++++++++++++++++++++++++++++++++-
>>>>>     1 file changed, 34 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>>>>> index b39d3f2898a1..1ea3046724f8 100644
>>>>> --- a/tools/lib/bpf/libbpf.c
>>>>> +++ b/tools/lib/bpf/libbpf.c
>>>>> @@ -446,13 +446,18 @@ struct bpf_program {
>>>>>         struct bpf_object *obj;
>>>>>
>>>>>         int fd;
>>>>> -     bool autoload;
>>>>> +     bool autoload:1;
>>>>> +     bool autoload_user_set:1;
>>>>>         bool autoattach;
>>>>>         bool sym_global;
>>>>>         bool mark_btf_static;
>>>>>         enum bpf_prog_type type;
>>>>>         enum bpf_attach_type expected_attach_type;
>>>>>         int exception_cb_idx;
>>>>> +     /* total number of struct_ops maps with autocreate == true
>>>>> +      * that reference this program
>>>>> +      */
>>>>> +     __u32 struct_ops_refs;
>>>>
>>>> Instead of adding struct_ops_refs and autoload_user_set,
>>>>
>>>> for BPF_PROG_TYPE_STRUCT_OPS, how about deciding to load it or not by checking
>>>> prog->attach_btf_id (non zero) alone. The prog->attach_btf_id is now decided at
>>>> load time and is only set if it is used by at least one autocreate map, if I
>>>> read patch 2 & 3 correctly.
>>>>
>>>> Meaning ignore prog->autoload*. Load the struct_ops prog as long as it is used
>>>> by one struct_ops map with autocreate == true.
>>>>
>>>> If the struct_ops prog is not used in any struct_ops map, the bpf prog cannot be
>>>> loaded even the autoload is set. If bpf prog is used in a struct_ops map and its
>>>> autoload is set to false, the struct_ops map will be in broken state. Thus,
>>>
>>> We can easily detect this condition and report meaningful error.
>>>
>>>> prog->autoload does not fit very well with struct_ops prog and may as well
>>>> depend on whether the struct_ops prog is used by a struct_ops map alone?
>>>
>>> I think it's probably fine from a usability standpoint to disable
>>> loading the BPF program if its struct_ops map was explicitly set to
>>> not auto-create. It's a bit of deviation from other program types, but
>>> in practice this logic will make it easier for users.
>>>
>>> One question I have, though, is whether we should report as an error a
>>> stand-alone struct_ops BPF program that is not used from any
>>> struct_ops map? Or should we load it nevertheless? Or should we
>>> silently not load it?
>>
>> I think the libbpf could report an error if the prog is not used in any
>> struct_ops map at the source code level, not sure if it is useful.
>>
>> However, it probably should not report error if that struct_ops map (where the
>> prog is resided) does not have autocreate set to true.
>>
>> If a BPF program is not used in any struct_ops map, it cannot be loaded anyway
>> because the prog->attach_btf_id is not set. If libbpf tries to load the prog,
>> the kernel will reject it also. I think it may be a question on whether it is
>> the user intention of not loading the prog if the prog is not used in any
>> struct_ops map. I tend to think it is the user intention of not loading it in
>> this case.
>>
>> SEC("struct_ops/test1")
>> int BPF_PROG(test1) { ... }
>>
>> SEC("struct_ops/test2")
>> int BPF_PROG(test2) { ... }
>>
>> SEC("?.struct_ops.link") struct some_ops___v1 a = { .test1 = test1 }
>> SEC("?.struct_ops.link") struct some_ops___v2 b = { .test1 = test1,
>>                                                     .test2 = test2, }
>>
>> In the above, the userspace may try to load with a newer some_ops___v2 first,
>> failed and then try a lower version some_ops___v1 and then succeeded. The test2
>> prog will not be used and not expected to be loaded.
>>
> 
> Yes, it's all sane in the above example. But imagine a stand-alone
> struct_ops program with no SEC(".struct_ops") at all:
> 
> 
> SEC("struct_ops/test1")
> int BPF_PROG(test1) { ... }
> 
> /* nothing else */
> 
> Currently this will fail, right?
> 
> And with your proposal it will succeed without actually even
> attempting to load the BPF program. Or am I misunderstanding?

Yep, currently it should fail.

Agree that we need to distinguish this case and prog->attach_btf_id is not 
enough. This probably can be tracked in collect_st_ops_relos at the open phase.
Eduard Zingerman Feb. 29, 2024, 12:40 a.m. UTC | #17
On Wed, 2024-02-28 at 16:37 -0800, Martin KaFai Lau wrote:
[...]

> > Yes, it's all sane in the above example. But imagine a stand-alone
> > struct_ops program with no SEC(".struct_ops") at all:
> > 
> > 
> > SEC("struct_ops/test1")
> > int BPF_PROG(test1) { ... }
> > 
> > /* nothing else */
> > 
> > Currently this will fail, right?
> > 
> > And with your proposal it will succeed without actually even
> > attempting to load the BPF program. Or am I misunderstanding?
> 
> Yep, currently it should fail.
> 
> Agree that we need to distinguish this case and prog->attach_btf_id is not 
> enough. This probably can be tracked in collect_st_ops_relos at the open phase.

collect_st_ops_relos() should work, I'll add a flag to track this.
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index b39d3f2898a1..1ea3046724f8 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -446,13 +446,18 @@  struct bpf_program {
 	struct bpf_object *obj;
 
 	int fd;
-	bool autoload;
+	bool autoload:1;
+	bool autoload_user_set:1;
 	bool autoattach;
 	bool sym_global;
 	bool mark_btf_static;
 	enum bpf_prog_type type;
 	enum bpf_attach_type expected_attach_type;
 	int exception_cb_idx;
+	/* total number of struct_ops maps with autocreate == true
+	 * that reference this program
+	 */
+	__u32 struct_ops_refs;
 
 	int prog_ifindex;
 	__u32 attach_btf_obj_fd;
@@ -4509,6 +4514,28 @@  static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info)
 	return 0;
 }
 
+/* Sync autoload and autocreate state between struct_ops map and
+ * referenced programs.
+ */
+static void bpf_map__struct_ops_toggle_progs_autoload(struct bpf_map *map, bool autocreate)
+{
+	struct bpf_program *prog;
+	int i;
+
+	for (i = 0; i < btf_vlen(map->st_ops->type); ++i) {
+		prog = map->st_ops->progs[i];
+
+		if (!prog || prog->autoload_user_set)
+			continue;
+
+		if (autocreate)
+			prog->struct_ops_refs++;
+		else
+			prog->struct_ops_refs--;
+		prog->autoload = prog->struct_ops_refs != 0;
+	}
+}
+
 bool bpf_map__autocreate(const struct bpf_map *map)
 {
 	return map->autocreate;
@@ -4519,6 +4546,9 @@  int bpf_map__set_autocreate(struct bpf_map *map, bool autocreate)
 	if (map->obj->loaded)
 		return libbpf_err(-EBUSY);
 
+	if (map->st_ops && map->autocreate != autocreate)
+		bpf_map__struct_ops_toggle_progs_autoload(map, autocreate);
+
 	map->autocreate = autocreate;
 	return 0;
 }
@@ -8801,6 +8831,7 @@  int bpf_program__set_autoload(struct bpf_program *prog, bool autoload)
 		return libbpf_err(-EINVAL);
 
 	prog->autoload = autoload;
+	prog->autoload_user_set = 1;
 	return 0;
 }
 
@@ -9428,6 +9459,8 @@  static int bpf_object__collect_st_ops_relos(struct bpf_object *obj,
 			return -EINVAL;
 		}
 
+		if (map->autocreate)
+			prog->struct_ops_refs++;
 		st_ops->progs[member_idx] = prog;
 	}