diff mbox series

[bpf-next,v4,06/11] libbpf: Support kernel module function calls

Message ID 20210920141526.3940002-7-memxor@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Support kernel module function calls from eBPF | expand

Checks

Context Check Description
bpf/vmtest-bpf-next fail VM_Test
bpf/vmtest-bpf-next-PR fail PR summary
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for bpf-next
netdev/subject_prefix success Link
netdev/cc_maintainers warning 2 maintainers not CCed: john.fastabend@gmail.com kpsingh@kernel.org
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch warning WARNING: line length of 86 exceeds 80 columns
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/header_inline success Link

Commit Message

Kumar Kartikeya Dwivedi Sept. 20, 2021, 2:15 p.m. UTC
This patch adds libbpf support for kernel module function call support.
The fd_array parameter is used during BPF program load is used to pass
module BTFs referenced by the program. insn->off is set to index into
this array, but starts from 1, because insn->off as 0 is reserved for
btf_vmlinux.

We try to use existing insn->off for a module, since the kernel limits
the maximum distinct module BTFs for kfuncs to 256, and also because
index must never exceed the maximum allowed value that can fit in
insn->off (INT16_MAX). In the future, if kernel interprets signed offset
as unsigned for kfunc calls, this limit can be increased to UINT16_MAX.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 tools/lib/bpf/bpf.c             |  1 +
 tools/lib/bpf/libbpf.c          | 58 +++++++++++++++++++++++++++++++--
 tools/lib/bpf/libbpf_internal.h |  1 +
 3 files changed, 57 insertions(+), 3 deletions(-)

Comments

Andrii Nakryiko Sept. 21, 2021, 10:41 p.m. UTC | #1
On Mon, Sep 20, 2021 at 7:15 AM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> This patch adds libbpf support for kernel module function call support.
> The fd_array parameter is used during BPF program load is used to pass
> module BTFs referenced by the program. insn->off is set to index into
> this array, but starts from 1, because insn->off as 0 is reserved for
> btf_vmlinux.
>
> We try to use existing insn->off for a module, since the kernel limits
> the maximum distinct module BTFs for kfuncs to 256, and also because
> index must never exceed the maximum allowed value that can fit in
> insn->off (INT16_MAX). In the future, if kernel interprets signed offset
> as unsigned for kfunc calls, this limit can be increased to UINT16_MAX.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
>  tools/lib/bpf/bpf.c             |  1 +
>  tools/lib/bpf/libbpf.c          | 58 +++++++++++++++++++++++++++++++--
>  tools/lib/bpf/libbpf_internal.h |  1 +
>  3 files changed, 57 insertions(+), 3 deletions(-)
>
> diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
> index 2401fad090c5..7d1741ceaa32 100644
> --- a/tools/lib/bpf/bpf.c
> +++ b/tools/lib/bpf/bpf.c
> @@ -264,6 +264,7 @@ int libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr)
>         attr.line_info_rec_size = load_attr->line_info_rec_size;
>         attr.line_info_cnt = load_attr->line_info_cnt;
>         attr.line_info = ptr_to_u64(load_attr->line_info);
> +       attr.fd_array = ptr_to_u64(load_attr->fd_array);
>
>         if (load_attr->name)
>                 memcpy(attr.prog_name, load_attr->name,
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index da65a1666a5e..3049dfc6088e 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -420,6 +420,12 @@ struct extern_desc {
>
>                         /* local btf_id of the ksym extern's type. */
>                         __u32 type_id;
> +                       /* offset to be patched in for insn->off,
> +                        * this is 0 for btf_vmlinux, and index + 1

What does "index + 1" mean here? Seems like kernel code is using the
offset as is, without any -1 compensation.

> +                        * for module BTF, where index is BTF index in
> +                        * obj->fd_array
> +                        */
> +                       __s16 offset;
>                 } ksym;
>         };
>  };
> @@ -516,6 +522,10 @@ struct bpf_object {
>         void *priv;
>         bpf_object_clear_priv_t clear_priv;
>
> +       int *fd_array;
> +       size_t fd_cap_cnt;
> +       int nr_fds;
> +
>         char path[];
>  };
>  #define obj_elf_valid(o)       ((o)->efile.elf)
> @@ -5357,6 +5367,7 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
>                         ext = &obj->externs[relo->sym_off];
>                         insn[0].src_reg = BPF_PSEUDO_KFUNC_CALL;
>                         insn[0].imm = ext->ksym.kernel_btf_id;
> +                       insn[0].off = ext->ksym.offset;
>                         break;
>                 case RELO_SUBPROG_ADDR:
>                         if (insn[0].src_reg != BPF_PSEUDO_FUNC) {
> @@ -6151,6 +6162,7 @@ load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt,
>         }
>         load_attr.log_level = prog->log_level;
>         load_attr.prog_flags = prog->prog_flags;
> +       load_attr.fd_array = prog->obj->fd_array;
>
>         if (prog->obj->gen_loader) {
>                 bpf_gen__prog_load(prog->obj->gen_loader, &load_attr,
> @@ -6763,9 +6775,46 @@ static int bpf_object__resolve_ksym_func_btf_id(struct bpf_object *obj,
>         }
>
>         if (kern_btf != obj->btf_vmlinux) {
> -               pr_warn("extern (func ksym) '%s': function in kernel module is not supported\n",
> -                       ext->name);
> -               return -ENOTSUP;
> +               int index = -1;
> +
> +               if (!obj->fd_array) {
> +                       obj->fd_array = calloc(8, sizeof(*obj->fd_array));
> +                       if (!obj->fd_array)
> +                               return -ENOMEM;
> +                       obj->fd_cap_cnt = 8;
> +                       /* index = 0 is for vmlinux BTF, so skip it */
> +                       obj->nr_fds = 1;
> +               }

this doesn't make sense, you use libbpf_ensure_mem() and shouldn't do
anything like this, it's all taken care of  already

> +
> +               for (int i = 0; i < obj->nr_fds; i++) {
> +                       if (obj->fd_array[i] == kern_btf_fd) {
> +                               index = i;
> +                               break;
> +                       }
> +               }

we can actually avoid all this. We already have a list of module BTFs
in bpf_object (obj->btf_modules), where we remember their id, fd, etc.
We can also remember their fd_arr_idx for quick lookup. Just teach
find_ksym_btf_id() to optionally return struct module_btf * and use
that to find/set idx. That seems cleaner and probably would be easier
in the future as well.

> +
> +               if (index == -1) {
> +                       if (obj->nr_fds == obj->fd_cap_cnt) {

don't check, libbpf_ensure_mem() handles that

> +                               ret = libbpf_ensure_mem((void **)&obj->fd_array,
> +                                                       &obj->fd_cap_cnt, sizeof(int),
> +                                                       obj->fd_cap_cnt + 1);
> +                               if (ret)
> +                                       return ret;
> +                       }
> +
> +                       index = obj->nr_fds;
> +                       obj->fd_array[obj->nr_fds++] = kern_btf_fd;
> +               }
> +
> +               if (index > INT16_MAX) {
> +                       /* insn->off is s16 */
> +                       pr_warn("extern (func ksym) '%s': module btf fd index too big\n",
> +                               ext->name);

can you log index value here as well? "module BTF FD index %d is too big\n"?

> +                       return -E2BIG;
> +               }
> +               ext->ksym.offset = index;

> +       } else {
> +               ext->ksym.offset = 0;
>         }

I think it will be cleaner if you move the entire offset determination
logic after all the other checks are performed and ext is mostly
populated. That will also make the logic shorter and simpler because
if you find kern_btf_fd match, you can exit early (or probably rather
goto to report the match and exit). Otherwise

>
>         kern_func = btf__type_by_id(kern_btf, kfunc_id);

this is actually extremely wasteful for module BTFs. Let's add
internal (at least for now) helper that will search only for "own" BTF
types in the BTF, skipping types in base BTF. Something like
btf_type_by_id_own()?

> @@ -6941,6 +6990,9 @@ int bpf_object__load_xattr(struct bpf_object_load_attr *attr)
>                         err = bpf_gen__finish(obj->gen_loader);
>         }
>
> +       /* clean up fd_array */
> +       zfree(&obj->fd_array);
> +
>         /* clean up module BTFs */
>         for (i = 0; i < obj->btf_module_cnt; i++) {
>                 close(obj->btf_modules[i].fd);
> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> index ceb0c98979bc..44b8f381b035 100644
> --- a/tools/lib/bpf/libbpf_internal.h
> +++ b/tools/lib/bpf/libbpf_internal.h
> @@ -291,6 +291,7 @@ struct bpf_prog_load_params {
>         __u32 log_level;
>         char *log_buf;
>         size_t log_buf_sz;
> +       int *fd_array;
>  };
>
>  int libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr);
> --
> 2.33.0
>
Kumar Kartikeya Dwivedi Sept. 24, 2021, 11:54 p.m. UTC | #2
On Wed, Sep 22, 2021 at 04:11:13AM IST, Andrii Nakryiko wrote:
> On Mon, Sep 20, 2021 at 7:15 AM Kumar Kartikeya Dwivedi
> <memxor@gmail.com> wrote:
> > [...]
> > +                       return -E2BIG;
> > +               }
> > +               ext->ksym.offset = index;
>
> > +       } else {
> > +               ext->ksym.offset = 0;
> >         }
>
> I think it will be cleaner if you move the entire offset determination
> logic after all the other checks are performed and ext is mostly
> populated. That will also make the logic shorter and simpler because
> if ayou find kern_btf_fd match, you can exit early (or probably rather

Ack to everything else (including the other mail), but...

> goto to report the match and exit). Otherwise
>

This sentence got eaten up.

> >
> >         kern_func = btf__type_by_id(kern_btf, kfunc_id);
>
> this is actually extremely wasteful for module BTFs. Let's add
> internal (at least for now) helper that will search only for "own" BTF
> types in the BTF, skipping types in base BTF. Something like
> btf_type_by_id_own()?
>

Just to make sure I am not misunderstanding: I don't see where this is wasteful.
btf_type_by_id seems to not be searching anything, but just returns pointer in
base BTF if kfunc_id < btf->start_id, otherwise in module BTF.

What am I missing? I guess the 'kern_btf' name was the source of confusion? If
so, I'll rename it.

Thanks.

--
Kartikeya
Andrii Nakryiko Sept. 25, 2021, 12:30 a.m. UTC | #3
On Fri, Sep 24, 2021 at 4:54 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Wed, Sep 22, 2021 at 04:11:13AM IST, Andrii Nakryiko wrote:
> > On Mon, Sep 20, 2021 at 7:15 AM Kumar Kartikeya Dwivedi
> > <memxor@gmail.com> wrote:
> > > [...]
> > > +                       return -E2BIG;
> > > +               }
> > > +               ext->ksym.offset = index;
> >
> > > +       } else {
> > > +               ext->ksym.offset = 0;
> > >         }
> >
> > I think it will be cleaner if you move the entire offset determination
> > logic after all the other checks are performed and ext is mostly
> > populated. That will also make the logic shorter and simpler because
> > if ayou find kern_btf_fd match, you can exit early (or probably rather
>
> Ack to everything else (including the other mail), but...
>
> > goto to report the match and exit). Otherwise
> >
>
> This sentence got eaten up.

No idea what I was going to say here, sorry... Sometimes Gmail UI
glitches with undo/redo, maybe that's what happened here. Doesn't
matter, ignore the "Otherwise" part.

>
> > >
> > >         kern_func = btf__type_by_id(kern_btf, kfunc_id);
> >
> > this is actually extremely wasteful for module BTFs. Let's add
> > internal (at least for now) helper that will search only for "own" BTF
> > types in the BTF, skipping types in base BTF. Something like
> > btf_type_by_id_own()?
> >
>
> Just to make sure I am not misunderstanding: I don't see where this is wasteful.
> btf_type_by_id seems to not be searching anything, but just returns pointer in
> base BTF if kfunc_id < btf->start_id, otherwise in module BTF.
>

Hm, sorry... Right sentiment and thought, but wrong piece of code to
quote it on.

I had in mind the btf__find_by_name_kind() use in find_ksym_btf_id().
Once we start going over each module, we shouldn't be re-checking
vmlinux BTF when doing btf__find_by_name_kind. It should only check
the types that each module BTF adds on top of vmlinux BTF. That's what
would be good to optimize, especially as more complicated BPF programs
will start using more ksym vars and funcs.


> What am I missing? I guess the 'kern_btf' name was the source of confusion? If
> so, I'll rename it.
>
> Thanks.
>
> --
> Kartikeya
diff mbox series

Patch

diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index 2401fad090c5..7d1741ceaa32 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -264,6 +264,7 @@  int libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr)
 	attr.line_info_rec_size = load_attr->line_info_rec_size;
 	attr.line_info_cnt = load_attr->line_info_cnt;
 	attr.line_info = ptr_to_u64(load_attr->line_info);
+	attr.fd_array = ptr_to_u64(load_attr->fd_array);
 
 	if (load_attr->name)
 		memcpy(attr.prog_name, load_attr->name,
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index da65a1666a5e..3049dfc6088e 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -420,6 +420,12 @@  struct extern_desc {
 
 			/* local btf_id of the ksym extern's type. */
 			__u32 type_id;
+			/* offset to be patched in for insn->off,
+			 * this is 0 for btf_vmlinux, and index + 1
+			 * for module BTF, where index is BTF index in
+			 * obj->fd_array
+			 */
+			__s16 offset;
 		} ksym;
 	};
 };
@@ -516,6 +522,10 @@  struct bpf_object {
 	void *priv;
 	bpf_object_clear_priv_t clear_priv;
 
+	int *fd_array;
+	size_t fd_cap_cnt;
+	int nr_fds;
+
 	char path[];
 };
 #define obj_elf_valid(o)	((o)->efile.elf)
@@ -5357,6 +5367,7 @@  bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
 			ext = &obj->externs[relo->sym_off];
 			insn[0].src_reg = BPF_PSEUDO_KFUNC_CALL;
 			insn[0].imm = ext->ksym.kernel_btf_id;
+			insn[0].off = ext->ksym.offset;
 			break;
 		case RELO_SUBPROG_ADDR:
 			if (insn[0].src_reg != BPF_PSEUDO_FUNC) {
@@ -6151,6 +6162,7 @@  load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt,
 	}
 	load_attr.log_level = prog->log_level;
 	load_attr.prog_flags = prog->prog_flags;
+	load_attr.fd_array = prog->obj->fd_array;
 
 	if (prog->obj->gen_loader) {
 		bpf_gen__prog_load(prog->obj->gen_loader, &load_attr,
@@ -6763,9 +6775,46 @@  static int bpf_object__resolve_ksym_func_btf_id(struct bpf_object *obj,
 	}
 
 	if (kern_btf != obj->btf_vmlinux) {
-		pr_warn("extern (func ksym) '%s': function in kernel module is not supported\n",
-			ext->name);
-		return -ENOTSUP;
+		int index = -1;
+
+		if (!obj->fd_array) {
+			obj->fd_array = calloc(8, sizeof(*obj->fd_array));
+			if (!obj->fd_array)
+				return -ENOMEM;
+			obj->fd_cap_cnt = 8;
+			/* index = 0 is for vmlinux BTF, so skip it */
+			obj->nr_fds = 1;
+		}
+
+		for (int i = 0; i < obj->nr_fds; i++) {
+			if (obj->fd_array[i] == kern_btf_fd) {
+				index = i;
+				break;
+			}
+		}
+
+		if (index == -1) {
+			if (obj->nr_fds == obj->fd_cap_cnt) {
+				ret = libbpf_ensure_mem((void **)&obj->fd_array,
+							&obj->fd_cap_cnt, sizeof(int),
+							obj->fd_cap_cnt + 1);
+				if (ret)
+					return ret;
+			}
+
+			index = obj->nr_fds;
+			obj->fd_array[obj->nr_fds++] = kern_btf_fd;
+		}
+
+		if (index > INT16_MAX) {
+			/* insn->off is s16 */
+			pr_warn("extern (func ksym) '%s': module btf fd index too big\n",
+				ext->name);
+			return -E2BIG;
+		}
+		ext->ksym.offset = index;
+	} else {
+		ext->ksym.offset = 0;
 	}
 
 	kern_func = btf__type_by_id(kern_btf, kfunc_id);
@@ -6941,6 +6990,9 @@  int bpf_object__load_xattr(struct bpf_object_load_attr *attr)
 			err = bpf_gen__finish(obj->gen_loader);
 	}
 
+	/* clean up fd_array */
+	zfree(&obj->fd_array);
+
 	/* clean up module BTFs */
 	for (i = 0; i < obj->btf_module_cnt; i++) {
 		close(obj->btf_modules[i].fd);
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index ceb0c98979bc..44b8f381b035 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -291,6 +291,7 @@  struct bpf_prog_load_params {
 	__u32 log_level;
 	char *log_buf;
 	size_t log_buf_sz;
+	int *fd_array;
 };
 
 int libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr);