Message ID | 13cba9e1c39e999e7bfb14f1f986b76d13e150b3.1646188795.git.delyank@fb.com (mailing list archive) |
---|---|
State | Superseded |
Delegated to: | BPF |
Headers | show |
Series | Subskeleton support for BPF libraries | expand |
Hi Delyan, On 3/2/22 3:48 AM, Delyan Kratunov wrote: > In symmetry with bpf_object__open_skeleton(), > bpf_object__open_subskeleton() performs the actual walking and linking > of symbols described by bpf_sym_skeleton objects. > > Signed-off-by: Delyan Kratunov <delyank@fb.com> > --- > tools/lib/bpf/libbpf.c | 76 ++++++++++++++++++++++++++++++++++++++++ > tools/lib/bpf/libbpf.h | 21 +++++++++++ > tools/lib/bpf/libbpf.map | 2 ++ > 3 files changed, 99 insertions(+) > Triggers CI failure with: > build_kernel - Building kernel libbpf.c: In function ‘bpf_object__open_subskeleton’: libbpf.c:11779:27: error: ‘i’ may be used uninitialized in this function [-Werror=maybe-uninitialized] 11779 | sym->section, s->syms[i].name); | ^ cc1: all warnings being treated as errors make[5]: *** [/tmp/runner/work/bpf/bpf/tools/build/Makefile.build:96: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf.o] Error 1 make[4]: *** [Makefile:157: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf-in.o] Error 2 make[3]: *** [Makefile:55: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids//libbpf/libbpf.a] Error 2 make[3]: *** Waiting for unfinished jobs.... make[2]: *** [Makefile:72: bpf/resolve_btfids] Error 2 make[1]: *** [Makefile:1334: tools/bpf/resolve_btfids] Error 2 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:350: __build_one_by_one] Error 2 Error: Process completed with exit code 2. Thanks, Daniel
On Wed, 2022-03-02 at 22:43 +0100, Daniel Borkmann wrote: > > Triggers CI failure with: > > > build_kernel - Building kernel > > libbpf.c: In function ‘bpf_object__open_subskeleton’: > libbpf.c:11779:27: error: ‘i’ may be used uninitialized in this function [-Werror=maybe-uninitialized] > 11779 | sym->section, s->syms[i].name); > | ^ > cc1: all warnings being treated as errors > make[5]: *** [/tmp/runner/work/bpf/bpf/tools/build/Makefile.build:96: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf.o] Error 1 > make[4]: *** [Makefile:157: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf-in.o] Error 2 > make[3]: *** [Makefile:55: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids//libbpf/libbpf.a] Error 2 > make[3]: *** Waiting for unfinished jobs.... > make[2]: *** [Makefile:72: bpf/resolve_btfids] Error 2 > make[1]: *** [Makefile:1334: tools/bpf/resolve_btfids] Error 2 > make[1]: *** Waiting for unfinished jobs.... > make: *** [Makefile:350: __build_one_by_one] Error 2 > Error: Process completed with exit code 2. > > Thanks, > Daniel Argh, sorry about that, sending reroll in a few.
On Wed, Mar 2, 2022 at 4:20 PM Delyan Kratunov <delyank@fb.com> wrote: > > On Wed, 2022-03-02 at 22:43 +0100, Daniel Borkmann wrote: > > > > Triggers CI failure with: > > > > > build_kernel - Building kernel > > > > libbpf.c: In function ‘bpf_object__open_subskeleton’: > > libbpf.c:11779:27: error: ‘i’ may be used uninitialized in this function [-Werror=maybe-uninitialized] > > 11779 | sym->section, s->syms[i].name); > > | ^ > > cc1: all warnings being treated as errors > > make[5]: *** [/tmp/runner/work/bpf/bpf/tools/build/Makefile.build:96: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf.o] Error 1 > > make[4]: *** [Makefile:157: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids/libbpf/staticobjs/libbpf-in.o] Error 2 > > make[3]: *** [Makefile:55: /tmp/runner/work/bpf/bpf/tools/bpf/resolve_btfids//libbpf/libbpf.a] Error 2 > > make[3]: *** Waiting for unfinished jobs.... > > make[2]: *** [Makefile:72: bpf/resolve_btfids] Error 2 > > make[1]: *** [Makefile:1334: tools/bpf/resolve_btfids] Error 2 > > make[1]: *** Waiting for unfinished jobs.... > > make: *** [Makefile:350: __build_one_by_one] Error 2 > > Error: Process completed with exit code 2. > > > > Thanks, > > Daniel > > Argh, sorry about that, sending reroll in a few. Please hold off until you get review feedback
On Wed, 2022-03-02 at 16:28 -0800, Andrii Nakryiko wrote:
> Please hold off until you get review feedback
Happy to wait, I discovered some functional issues in the test once rodata and
bpf helpers are involved in the lib. I have some debugging to do anyway.
On Tue, Mar 1, 2022 at 6:49 PM Delyan Kratunov <delyank@fb.com> wrote: > > In symmetry with bpf_object__open_skeleton(), > bpf_object__open_subskeleton() performs the actual walking and linking > of symbols described by bpf_sym_skeleton objects. > > Signed-off-by: Delyan Kratunov <delyank@fb.com> > --- > tools/lib/bpf/libbpf.c | 76 ++++++++++++++++++++++++++++++++++++++++ > tools/lib/bpf/libbpf.h | 21 +++++++++++ > tools/lib/bpf/libbpf.map | 2 ++ > 3 files changed, 99 insertions(+) > > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c > index d20ae8f225ee..e6c27f4b9dea 100644 > --- a/tools/lib/bpf/libbpf.c > +++ b/tools/lib/bpf/libbpf.c > @@ -11748,6 +11748,82 @@ int bpf_object__open_skeleton(struct bpf_object_skeleton *s, > return 0; > } > > +int bpf_object__open_subskeleton(struct bpf_object_subskeleton *s) > +{ > + int i, len, map_type_id, sym_idx; > + const char *var_name; > + struct bpf_map *map; > + struct btf *btf; > + const struct btf_type *map_type, *var_type; > + const struct bpf_sym_skeleton *sym; > + struct btf_var_secinfo *var; > + struct bpf_map *last_map = NULL; > + const struct btf_type *last_map_type = NULL; > + > + if (!s->obj) > + return libbpf_err(-EINVAL); > + > + btf = bpf_object__btf(s->obj); > + if (!btf) > + return libbpf_err(errno); -errno > + > + for (sym_idx = 0; sym_idx < s->sym_cnt; sym_idx++) { > + sym = &s->syms[sym_idx]; > + if (last_map && (strcmp(sym->section, bpf_map__section_name(last_map)) == 0)) { see above about having struct bpf_map ** instead of sym->section > + map = last_map; > + map_type = last_map_type; > + } else { > + map = bpf_object__find_map_by_name(s->obj, sym->section); > + if (!map) { > + pr_warn("Could not find map for section %1$s, symbol %2$s", > + sym->section, s->syms[i].name); > + return libbpf_err(-EINVAL); > + } > + map_type_id = btf__find_by_name_kind(btf, sym->section, BTF_KIND_DATASEC); bpf_map__btf_value_type_id() ? > + if (map_type_id < 0) { > + pr_warn("Could not find map type in btf for section %1$s (due to symbol %2$s)", > + sym->section, sym->name); > + return libbpf_err(-EINVAL); > + } > + map_type = btf__type_by_id(btf, map_type_id); > + } > + [...] > + > +void bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s) > +{ > + if (!s) > + return; > + if (s->syms) > + free(s->syms); no need to check s->syms, free handles NULL just fine > + free(s); > +} > + > int bpf_object__load_skeleton(struct bpf_object_skeleton *s) > { > int i, err; > diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h > index 7b66794f1c0a..915d59c31ad5 100644 > --- a/tools/lib/bpf/libbpf.h > +++ b/tools/lib/bpf/libbpf.h > @@ -1291,6 +1291,27 @@ LIBBPF_API int bpf_object__attach_skeleton(struct bpf_object_skeleton *s); > LIBBPF_API void bpf_object__detach_skeleton(struct bpf_object_skeleton *s); > LIBBPF_API void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s); > > +struct bpf_sym_skeleton { I tried to get used to this "sym" terminology for a bit, but it still feels off. From user's perspective all this are variables. Any objections to use "var" terminology? > + const char *name; > + const char *section; what if we store a pointer to struct bpf_map * instead, that way we won't need to search, we'll just have a pointer ready > + void **addr; > +}; > + > +struct bpf_object_subskeleton { > + size_t sz; /* size of this struct, for forward/backward compatibility */ > + > + const struct bpf_object *obj; > + > + int sym_cnt; > + int sym_skel_sz; > + struct bpf_sym_skeleton *syms; as mentioned in previous patch, let's also record maps and prog, it is important and needed from the very beginning > +}; > + > +LIBBPF_API int > +bpf_object__open_subskeleton(struct bpf_object_subskeleton *s); > +LIBBPF_API void > +bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s); > + > struct gen_loader_opts { > size_t sz; /* size of this struct, for forward/backward compatiblity */ > const char *data; > diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map > index 5c85d297d955..81a1d0259866 100644 > --- a/tools/lib/bpf/libbpf.map > +++ b/tools/lib/bpf/libbpf.map > @@ -443,4 +443,6 @@ LIBBPF_0.7.0 { > LIBBPF_0.8.0 { > global: > bpf_map__section_name; > + bpf_object__open_subskeleton; > + bpf_object__destroy_subskeleton; indentation looks off here... global should be indented with one tab, and then APIs with two tabs > } LIBBPF_0.7.0; > -- > 2.34.1
On Wed, Mar 2, 2022 at 8:33 PM Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote: > > On Tue, Mar 1, 2022 at 6:49 PM Delyan Kratunov <delyank@fb.com> wrote: > > > > In symmetry with bpf_object__open_skeleton(), > > bpf_object__open_subskeleton() performs the actual walking and linking > > of symbols described by bpf_sym_skeleton objects. > > > > Signed-off-by: Delyan Kratunov <delyank@fb.com> > > --- > > tools/lib/bpf/libbpf.c | 76 ++++++++++++++++++++++++++++++++++++++++ > > tools/lib/bpf/libbpf.h | 21 +++++++++++ > > tools/lib/bpf/libbpf.map | 2 ++ > > 3 files changed, 99 insertions(+) > > forgot to mention, this patch logically probably should go before bpftool changes: 1) define types and APIs in libbpf, and only then 2) "use" those in bpftool > > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c > > index d20ae8f225ee..e6c27f4b9dea 100644 > > --- a/tools/lib/bpf/libbpf.c > > +++ b/tools/lib/bpf/libbpf.c > > @@ -11748,6 +11748,82 @@ int bpf_object__open_skeleton(struct bpf_object_skeleton *s, > > return 0; > > } > > > > +int bpf_object__open_subskeleton(struct bpf_object_subskeleton *s) > > +{ > > + int i, len, map_type_id, sym_idx; > > + const char *var_name; > > + struct bpf_map *map; > > + struct btf *btf; > > + const struct btf_type *map_type, *var_type; > > + const struct bpf_sym_skeleton *sym; > > + struct btf_var_secinfo *var; > > + struct bpf_map *last_map = NULL; > > + const struct btf_type *last_map_type = NULL; > > + > > + if (!s->obj) > > + return libbpf_err(-EINVAL); > > + > > + btf = bpf_object__btf(s->obj); > > + if (!btf) > > + return libbpf_err(errno); > > -errno > > > + > > + for (sym_idx = 0; sym_idx < s->sym_cnt; sym_idx++) { > > + sym = &s->syms[sym_idx]; > > + if (last_map && (strcmp(sym->section, bpf_map__section_name(last_map)) == 0)) { > > see above about having struct bpf_map ** instead of sym->section > > > + map = last_map; > > + map_type = last_map_type; > > + } else { > > + map = bpf_object__find_map_by_name(s->obj, sym->section); > > + if (!map) { > > + pr_warn("Could not find map for section %1$s, symbol %2$s", > > + sym->section, s->syms[i].name); > > + return libbpf_err(-EINVAL); > > + } > > + map_type_id = btf__find_by_name_kind(btf, sym->section, BTF_KIND_DATASEC); > > bpf_map__btf_value_type_id() ? > > > + if (map_type_id < 0) { > > + pr_warn("Could not find map type in btf for section %1$s (due to symbol %2$s)", > > + sym->section, sym->name); > > + return libbpf_err(-EINVAL); > > + } > > + map_type = btf__type_by_id(btf, map_type_id); > > + } > > + > > [...] > > > + > > +void bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s) > > +{ > > + if (!s) > > + return; > > + if (s->syms) > > + free(s->syms); > > no need to check s->syms, free handles NULL just fine > > > + free(s); > > +} > > + > > int bpf_object__load_skeleton(struct bpf_object_skeleton *s) > > { > > int i, err; > > diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h > > index 7b66794f1c0a..915d59c31ad5 100644 > > --- a/tools/lib/bpf/libbpf.h > > +++ b/tools/lib/bpf/libbpf.h > > @@ -1291,6 +1291,27 @@ LIBBPF_API int bpf_object__attach_skeleton(struct bpf_object_skeleton *s); > > LIBBPF_API void bpf_object__detach_skeleton(struct bpf_object_skeleton *s); > > LIBBPF_API void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s); > > > > +struct bpf_sym_skeleton { > > I tried to get used to this "sym" terminology for a bit, but it still > feels off. From user's perspective all this are variables. Any > objections to use "var" terminology? > > > + const char *name; > > + const char *section; > > what if we store a pointer to struct bpf_map * instead, that way we > won't need to search, we'll just have a pointer ready > > > + void **addr; > > +}; > > + > > +struct bpf_object_subskeleton { > > + size_t sz; /* size of this struct, for forward/backward compatibility */ > > + > > + const struct bpf_object *obj; > > + > > + int sym_cnt; > > + int sym_skel_sz; > > + struct bpf_sym_skeleton *syms; > > as mentioned in previous patch, let's also record maps and prog, it is > important and needed from the very beginning > > > +}; > > + > > +LIBBPF_API int > > +bpf_object__open_subskeleton(struct bpf_object_subskeleton *s); > > +LIBBPF_API void > > +bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s); > > + > > struct gen_loader_opts { > > size_t sz; /* size of this struct, for forward/backward compatiblity */ > > const char *data; > > diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map > > index 5c85d297d955..81a1d0259866 100644 > > --- a/tools/lib/bpf/libbpf.map > > +++ b/tools/lib/bpf/libbpf.map > > @@ -443,4 +443,6 @@ LIBBPF_0.7.0 { > > LIBBPF_0.8.0 { > > global: > > bpf_map__section_name; > > + bpf_object__open_subskeleton; > > + bpf_object__destroy_subskeleton; > > indentation looks off here... global should be indented with one tab, > and then APIs with two tabs > > > } LIBBPF_0.7.0; > > -- > > 2.34.1
On Wed, 2022-03-02 at 20:34 -0800, Andrii Nakryiko wrote: > > > > forgot to mention, this patch logically probably should go before > bpftool changes: 1) define types and APIs in libbpf, and only then 2) > "use" those in bpftool Sure. > > > > > > +struct bpf_sym_skeleton { > > > > I tried to get used to this "sym" terminology for a bit, but it still > > feels off. From user's perspective all this are variables. Any > > objections to use "var" terminology? "var" has a specific meaning in btf and I didn't want to make bpf_var_skeleton look related to btf_var for example. Given the extern usage that libs require, I figured "sym" would make sense to the user. If you don't think the confusion with btf_var is significant, I can rename it - this is all used by generated code anyway. > > > > > + const char *name; > > > + const char *section; > > > > what if we store a pointer to struct bpf_map * instead, that way we > > won't need to search, we'll just have a pointer ready We'd have to search *somewhere*. I'd rather have the search inside libbpf than inside the generated code. Besides, finding the right bpf_map from within the subskeleton is pretty annoying - you'll have to do suffix searches on the bpf_map names in the passed-in bpf_object and codegening all that is unnecessary when libbpf can look at real_name. > Thanks, Delyan
On Thu, Mar 3, 2022 at 11:09 AM Delyan Kratunov <delyank@fb.com> wrote: > > On Wed, 2022-03-02 at 20:34 -0800, Andrii Nakryiko wrote: > > > > > > > forgot to mention, this patch logically probably should go before > > bpftool changes: 1) define types and APIs in libbpf, and only then 2) > > "use" those in bpftool > > Sure. > > > > > > > > > +struct bpf_sym_skeleton { > > > > > > I tried to get used to this "sym" terminology for a bit, but it still > > > feels off. From user's perspective all this are variables. Any > > > objections to use "var" terminology? > > "var" has a specific meaning in btf and I didn't want to make bpf_var_skeleton > look related to btf_var for example. Given the extern usage that libs require, I > figured "sym" would make sense to the user. > Even for extern cases, we only generate stuff that really is a variable. That is, it has allocated memory and there is specific value. Only .kconfig is like that. .ksyms, for example, doesn't get any exposure in skeleton as it can't be used from user-space code. For me, symbol is just way too generic (could be basically anything, including ELF section symbol, function, etc, etc). But our use case is always variables available to both user-space and BPF code. I don't think btf_var vs btf_var_skeleton confusion is significant (and even then, each extern in .kconfig has corresponding BTF_KIND_VAR, so it all is in sync). > If you don't think the confusion with btf_var is significant, I can rename it - > this is all used by generated code anyway. > > > > > > > > + const char *name; > > > > + const char *section; > > > > > > what if we store a pointer to struct bpf_map * instead, that way we > > > won't need to search, we'll just have a pointer ready > > We'd have to search *somewhere*. I'd rather have the search inside libbpf than > inside the generated code. Besides, finding the right bpf_map from within the > subskeleton is pretty annoying - you'll have to do suffix searches on the > bpf_map names in the passed-in bpf_object and codegening all that is unnecessary > when libbpf can look at real_name. I think you misunderstood what I proposed. There is no explicit searching. Here is a simple example of sub-skeleton struct and how code-generated code will fill it out struct my_subskel { struct { struct bpf_map *my_map; } maps; struct my_subskel__data { int *my_var; } data; }; /* in codegen'ed code */ struct my_subskel *s; subskel->syms[0].name = "my_var"; subskel->syms[0].map = &s->maps.data_syn; It's similar in principle to how we define maps (that are found and filled out by libbpf): s->maps[4].name = ".data.dyn"; s->maps[4].map = &obj->maps.data_dyn; s->maps[4].mmaped = (void **)&obj->data_dyn; Except in this case we use &s->maps.data_syn for reading, not for writing into it. Hope this is clearer now. > > > > > Thanks, > Delyan
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index d20ae8f225ee..e6c27f4b9dea 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -11748,6 +11748,82 @@ int bpf_object__open_skeleton(struct bpf_object_skeleton *s, return 0; } +int bpf_object__open_subskeleton(struct bpf_object_subskeleton *s) +{ + int i, len, map_type_id, sym_idx; + const char *var_name; + struct bpf_map *map; + struct btf *btf; + const struct btf_type *map_type, *var_type; + const struct bpf_sym_skeleton *sym; + struct btf_var_secinfo *var; + struct bpf_map *last_map = NULL; + const struct btf_type *last_map_type = NULL; + + if (!s->obj) + return libbpf_err(-EINVAL); + + btf = bpf_object__btf(s->obj); + if (!btf) + return libbpf_err(errno); + + for (sym_idx = 0; sym_idx < s->sym_cnt; sym_idx++) { + sym = &s->syms[sym_idx]; + if (last_map && (strcmp(sym->section, bpf_map__section_name(last_map)) == 0)) { + map = last_map; + map_type = last_map_type; + } else { + map = bpf_object__find_map_by_name(s->obj, sym->section); + if (!map) { + pr_warn("Could not find map for section %1$s, symbol %2$s", + sym->section, s->syms[i].name); + return libbpf_err(-EINVAL); + } + map_type_id = btf__find_by_name_kind(btf, sym->section, BTF_KIND_DATASEC); + if (map_type_id < 0) { + pr_warn("Could not find map type in btf for section %1$s (due to symbol %2$s)", + sym->section, sym->name); + return libbpf_err(-EINVAL); + } + map_type = btf__type_by_id(btf, map_type_id); + } + + /* We have a section and a corresponding type, now find the + * symbol in the loaded map. This is clearly quadratic in the + * number of symbols in the section, but that's easy to optimize + * once the need arises. + */ + + len = btf_vlen(map_type); + for (i = 0, var = btf_var_secinfos(map_type); i < len; i++, var++) { + var_type = btf__type_by_id(btf, var->type); + if (!var_type) { + pr_warn("Could not find var type for item %1$d in section %2$s", + i, sym->section); + return libbpf_err(-EINVAL); + } + var_name = btf__name_by_offset(btf, var_type->name_off); + if (strcmp(var_name, sym->name) == 0) { + *sym->addr = (char *) map->mmaped + var->offset; + break; + } + } + + last_map = map; + last_map_type = map_type; + } + return 0; +} + +void bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s) +{ + if (!s) + return; + if (s->syms) + free(s->syms); + free(s); +} + int bpf_object__load_skeleton(struct bpf_object_skeleton *s) { int i, err; diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 7b66794f1c0a..915d59c31ad5 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1291,6 +1291,27 @@ LIBBPF_API int bpf_object__attach_skeleton(struct bpf_object_skeleton *s); LIBBPF_API void bpf_object__detach_skeleton(struct bpf_object_skeleton *s); LIBBPF_API void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s); +struct bpf_sym_skeleton { + const char *name; + const char *section; + void **addr; +}; + +struct bpf_object_subskeleton { + size_t sz; /* size of this struct, for forward/backward compatibility */ + + const struct bpf_object *obj; + + int sym_cnt; + int sym_skel_sz; + struct bpf_sym_skeleton *syms; +}; + +LIBBPF_API int +bpf_object__open_subskeleton(struct bpf_object_subskeleton *s); +LIBBPF_API void +bpf_object__destroy_subskeleton(struct bpf_object_subskeleton *s); + struct gen_loader_opts { size_t sz; /* size of this struct, for forward/backward compatiblity */ const char *data; diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index 5c85d297d955..81a1d0259866 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -443,4 +443,6 @@ LIBBPF_0.7.0 { LIBBPF_0.8.0 { global: bpf_map__section_name; + bpf_object__open_subskeleton; + bpf_object__destroy_subskeleton; } LIBBPF_0.7.0;
In symmetry with bpf_object__open_skeleton(), bpf_object__open_subskeleton() performs the actual walking and linking of symbols described by bpf_sym_skeleton objects. Signed-off-by: Delyan Kratunov <delyank@fb.com> --- tools/lib/bpf/libbpf.c | 76 ++++++++++++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.h | 21 +++++++++++ tools/lib/bpf/libbpf.map | 2 ++ 3 files changed, 99 insertions(+)