diff mbox series

[bpf-next,v2,5/5] selftests/bpf: test subskeleton functionality

Message ID f262f63b36d00d4a77d1166bcaffe7684b6ebbee.1646957399.git.delyank@fb.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Subskeleton support for BPF libraries | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 8 maintainers not CCed: linux-kselftest@vger.kernel.org kpsingh@kernel.org john.fastabend@gmail.com kafai@fb.com shuah@kernel.org songliubraving@fb.com yhs@fb.com netdev@vger.kernel.org
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch fail CHECK: No space is necessary after a cast CHECK: Please don't use multiple blank lines ERROR: Using weak declarations can have unintended link defects WARNING: Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: externs should be avoided in .c files
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next success VM_Test

Commit Message

Delyan Kratunov March 11, 2022, 12:12 a.m. UTC
This patch changes the selftests/bpf Makefile to  also generate
a subskel.h for every skel.h it would have normally generated.

Separately, it also introduces a new subskeleton test which tests
library objects, externs, weak symbols, kconfigs, and user maps.

Signed-off-by: Delyan Kratunov <delyank@fb.com>
---
 tools/testing/selftests/bpf/.gitignore        |  1 +
 tools/testing/selftests/bpf/Makefile          | 10 ++-
 .../selftests/bpf/prog_tests/subskeleton.c    | 83 +++++++++++++++++++
 .../selftests/bpf/progs/test_subskeleton.c    | 23 +++++
 .../bpf/progs/test_subskeleton_lib.c          | 56 +++++++++++++
 .../bpf/progs/test_subskeleton_lib2.c         | 16 ++++
 6 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/subskeleton.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c

Comments

Andrii Nakryiko March 11, 2022, 11:40 p.m. UTC | #1
On Thu, Mar 10, 2022 at 4:12 PM Delyan Kratunov <delyank@fb.com> wrote:
>
> This patch changes the selftests/bpf Makefile to  also generate
> a subskel.h for every skel.h it would have normally generated.
>
> Separately, it also introduces a new subskeleton test which tests
> library objects, externs, weak symbols, kconfigs, and user maps.
>
> Signed-off-by: Delyan Kratunov <delyank@fb.com>
> ---
>  tools/testing/selftests/bpf/.gitignore        |  1 +
>  tools/testing/selftests/bpf/Makefile          | 10 ++-
>  .../selftests/bpf/prog_tests/subskeleton.c    | 83 +++++++++++++++++++
>  .../selftests/bpf/progs/test_subskeleton.c    | 23 +++++
>  .../bpf/progs/test_subskeleton_lib.c          | 56 +++++++++++++
>  .../bpf/progs/test_subskeleton_lib2.c         | 16 ++++
>  6 files changed, 188 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/subskeleton.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c
>
> diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
> index a7eead8820a0..595565eb68c0 100644
> --- a/tools/testing/selftests/bpf/.gitignore
> +++ b/tools/testing/selftests/bpf/.gitignore
> @@ -31,6 +31,7 @@ test_tcp_check_syncookie_user
>  test_sysctl
>  xdping
>  test_cpp
> +*.subskel.h
>  *.skel.h
>  *.lskel.h
>  /no_alu32
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index fe12b4f5fe20..9f7b22faedd6 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -328,6 +328,12 @@ SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
>  LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h          \
>                 linked_vars.skel.h linked_maps.skel.h
>
> +# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
> +# but that's created as a side-effect of the skel.h generation.
> +LINKED_SKELS += test_subskeleton.skel.h test_subskeleton_lib.skel.h

this can be part of LINKED_SKELS list above, no need to split
definition. The comment above is very useful, but can stay near -deps
definitions without hurting understanding, IMO

> +test_subskeleton.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o test_subskeleton.o
> +test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o
> +
>  LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \
>         test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c \
>         map_ptr_kern.c core_kern.c core_kern_overflow.c
> @@ -404,6 +410,7 @@ $(TRUNNER_BPF_SKELS): %.skel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
>         $(Q)$$(BPFTOOL) gen object $$(<:.o=.linked3.o) $$(<:.o=.linked2.o)
>         $(Q)diff $$(<:.o=.linked2.o) $$(<:.o=.linked3.o)
>         $(Q)$$(BPFTOOL) gen skeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.o=)) > $$@
> +       $(Q)$$(BPFTOOL) gen subskeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.o=)) > $$(@:.skel.h=.subskel.h)

we shouldn't need or use name for subskeleton (in real life you won't
know the name of the final bpf_object)

>
>  $(TRUNNER_BPF_LSKELS): %.lskel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
>         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> @@ -421,6 +428,7 @@ $(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_BPF_OBJS) $(BPFTOOL) | $(TRUNNER_OUTPUT)
>         $(Q)diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
>         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
>         $(Q)$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@
> +       $(Q)$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h)

probably don't need subskel for LSKELS (and it just adds race when we
generate both skeleton and light skeleton for the same object file)

>  endif
>
>  # ensure we set up tests.h header generation rule just once
> @@ -557,6 +565,6 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
>  EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \
>         prog_tests/tests.h map_tests/tests.h verifier/tests.h           \
>         feature bpftool                                                 \
> -       $(addprefix $(OUTPUT)/,*.o *.skel.h *.lskel.h no_alu32 bpf_gcc bpf_testmod.ko)
> +       $(addprefix $(OUTPUT)/,*.o *.skel.h *.lskel.h *.subskel.h no_alu32 bpf_gcc bpf_testmod.ko)
>
>  .PHONY: docs docs-clean
> diff --git a/tools/testing/selftests/bpf/prog_tests/subskeleton.c b/tools/testing/selftests/bpf/prog_tests/subskeleton.c
> new file mode 100644
> index 000000000000..9cbe17281f17
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/subskeleton.c
> @@ -0,0 +1,83 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) Meta Platforms, Inc. and affiliates. */
> +
> +#include <test_progs.h>
> +#include "test_subskeleton.skel.h"
> +#include "test_subskeleton_lib.subskel.h"
> +
> +void subskeleton_lib_setup(struct bpf_object *obj)

static?

> +{
> +       struct test_subskeleton_lib *lib = test_subskeleton_lib__open(obj);
> +
> +       if (!ASSERT_OK_PTR(lib, "open subskeleton"))
> +               goto out;

nit: can return early, if open failed that lib should be NULL, right?

> +
> +       *lib->rodata.var1 = 1;
> +       *lib->data.var2 = 2;
> +       lib->bss.var3->var3_1 = 3;
> +       lib->bss.var3->var3_2 = 4;
> +
> +out:
> +       test_subskeleton_lib__destroy(lib);
> +}
> +
> +int subskeleton_lib_subresult(struct bpf_object *obj)

static?

> +{
> +       struct test_subskeleton_lib *lib = test_subskeleton_lib__open(obj);
> +       int result;
> +
> +       if (!ASSERT_OK_PTR(lib, "open subskeleton")) {
> +               result = -EINVAL;
> +               goto out;
> +       }

same, just return ?


> +
> +       result = *lib->bss.libout1;
> +       ASSERT_EQ(result, 1 + 2 + 3 + 4 + 5 + 6, "lib subresult");
> +
> +       ASSERT_OK_PTR(lib->progs.lib_perf_handler, "lib_perf_handler");
> +       ASSERT_STREQ(bpf_program__name(lib->progs.lib_perf_handler),
> +                    "lib_perf_handler", "program name");
> +
> +       ASSERT_OK_PTR(lib->maps.map1, "map1");
> +       ASSERT_STREQ(bpf_map__name(lib->maps.map1), "map1", "map name");
> +
> +       ASSERT_EQ(*lib->data.var5, 5, "__weak var5");
> +       ASSERT_EQ(*lib->data.var6, 6, "extern var6");
> +       ASSERT_TRUE(*lib->kconfig.CONFIG_BPF_SYSCALL, "CONFIG_BPF_SYSCALL");
> +
> +out:
> +       test_subskeleton_lib__destroy(lib);
> +       return result;
> +}
> +
> +void test_subskeleton(void)
> +{
> +       int err, result;
> +       struct test_subskeleton *skel;
> +
> +       skel = test_subskeleton__open();
> +       if (!ASSERT_OK_PTR(skel, "skel_open"))
> +               return;
> +
> +       skel->rodata->rovar1 = 10;
> +       skel->rodata->var1 = 1;
> +       subskeleton_lib_setup(skel->obj);
> +
> +       err = test_subskeleton__load(skel);
> +       if (!ASSERT_OK(err, "skel_load"))
> +               goto cleanup;
> +
> +

nit: extra empty line

> +       err = test_subskeleton__attach(skel);
> +       if (!ASSERT_OK(err, "skel_attach"))
> +               goto cleanup;
> +
> +       /* trigger tracepoint */
> +       usleep(1);
> +
> +       result = subskeleton_lib_subresult(skel->obj) * 10;
> +       ASSERT_EQ(skel->bss->out1, result, "unexpected calculation");
> +
> +cleanup:
> +       test_subskeleton__destroy(skel);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton.c b/tools/testing/selftests/bpf/progs/test_subskeleton.c
> new file mode 100644
> index 000000000000..5bd5452b41cd
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_subskeleton.c
> @@ -0,0 +1,23 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) Meta Platforms, Inc. and affiliates. */
> +
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +
> +/* volatile to force a read, compiler may assume 0 otherwise */
> +const volatile int rovar1;
> +int out1;
> +
> +/* Override weak symbol in test_subskeleton_lib */
> +int var5 = 5;
> +

can you please add CONFIG_BPF_SYSCALL here as well, to check that
externs are properly "merged" and found, even if they overlap between
library and app BPF code

> +extern int lib_routine(void);
> +
> +SEC("raw_tp/sys_enter")
> +int handler1(const void *ctx)
> +{
> +       out1 = lib_routine() * rovar1;
> +       return 0;
> +}
> +
> +char LICENSE[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c b/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
> new file mode 100644
> index 000000000000..665338006e33
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
> @@ -0,0 +1,56 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) Meta Platforms, Inc. and affiliates. */
> +
> +#include <stdbool.h>
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +
> +/* volatile to force a read */
> +const volatile int var1;
> +volatile int var2 = 1;
> +struct {
> +       int var3_1;
> +       __s64 var3_2;
> +} var3;
> +int libout1;
> +
> +extern volatile bool CONFIG_BPF_SYSCALL __kconfig;
> +
> +int var4[4];
> +
> +__weak int var5 SEC(".data");
> +extern int var6;
> +int (*fn_ptr)(void);
> +

libbpf supports .data.my_custom_name and .rodata.my_custom_whatever,
let's have a variable to test this also works?


> +struct {
> +       __uint(type, BPF_MAP_TYPE_HASH);
> +       __type(key, __u32);
> +       __type(value, __u32);
> +       __uint(max_entries, 16);
> +} map1 SEC(".maps");
> +
> +extern struct {
> +       __uint(type, BPF_MAP_TYPE_HASH);
> +       __type(key, __u32);
> +       __type(value, __u32);
> +       __uint(max_entries, 16);
> +} map2 SEC(".maps");
> +
> +int lib_routine(void)
> +{
> +       __u32 key = 1, value = 2;
> +
> +       (void) CONFIG_BPF_SYSCALL;
> +       bpf_map_update_elem(&map2, &key, &value, BPF_ANY);
> +
> +       libout1 = var1 + var2 + var3.var3_1 + var3.var3_2 + var5 + var6;
> +       return libout1;
> +}
> +
> +SEC("perf_event")
> +int lib_perf_handler(struct pt_regs *ctx)
> +{
> +       return 0;
> +}
> +
> +char LICENSE[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c b/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c
> new file mode 100644
> index 000000000000..cbff92674b76
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c
> @@ -0,0 +1,16 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) Meta Platforms, Inc. and affiliates. */
> +
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +
> +struct {
> +       __uint(type, BPF_MAP_TYPE_HASH);
> +       __type(key, __u32);
> +       __type(value, __u32);
> +       __uint(max_entries, 16);
> +} map2 SEC(".maps");
> +

let's move this into progs/test_subskeleton.c instead. It will
simulate a bit more complicated scenario, where library expects
application to define and provide a map, but the library itself
doesn't define it. It should work just fine right now (I think), but
just in case let's double check that having only "extern map" in the
library works.

> +int var6 = 6;
> +
> +char LICENSE[] SEC("license") = "GPL";
> --
> 2.34.1
Delyan Kratunov March 14, 2022, 11:50 p.m. UTC | #2
On Fri, 2022-03-11 at 15:40 -0800, Andrii Nakryiko wrote:
> 
> we shouldn't need or use name for subskeleton (in real life you won't
> know the name of the final bpf_object)

Let's have this discussion in the bpftool email thread. Happy to remove the name
in the Makefile and fall back on the filename though.

> > 
> >  $(TRUNNER_BPF_LSKELS): %.lskel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
> >         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> > @@ -421,6 +428,7 @@ $(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_BPF_OBJS) $(BPFTOOL) | $(TRUNNER_OUTPUT)
> >         $(Q)diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
> >         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> >         $(Q)$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@
> > +       $(Q)$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h)
> 
> probably don't need subskel for LSKELS (and it just adds race when we
> generate both skeleton and light skeleton for the same object file)

We're not generating subskels for LSKELS, that's just confusing diff output.
This is under the $(TRUNNER_BPF_SKELS_LINKED) outputs.

> 
> can you please add CONFIG_BPF_SYSCALL here as well, to check that
> externs are properly "merged" and found, even if they overlap between
> library and app BPF code

Sure.

> 
> libbpf supports .data.my_custom_name and .rodata.my_custom_whatever,
> let's have a variable to test this also works?

Sure.

> 
> let's move this into progs/test_subskeleton.c instead. It will
> simulate a bit more complicated scenario, where library expects
> application to define and provide a map, but the library itself
> doesn't define it. It should work just fine right now (I think), but
> just in case let's double check that having only "extern map" in the
> library works.

This fails to even open in bpftool:

libbpf: map 'map2': unsupported map linkage extern.
Error: failed to open BPF object file: Operation not supported

If we think this is valuable enough to support, let's tackle it separately after
the bulk of this functionality is merged?


-- Delyan
Andrii Nakryiko March 15, 2022, 6:31 p.m. UTC | #3
On Mon, Mar 14, 2022 at 4:50 PM Delyan Kratunov <delyank@fb.com> wrote:
>
> On Fri, 2022-03-11 at 15:40 -0800, Andrii Nakryiko wrote:
> >
> > we shouldn't need or use name for subskeleton (in real life you won't
> > know the name of the final bpf_object)
>
> Let's have this discussion in the bpftool email thread. Happy to remove the name
> in the Makefile and fall back on the filename though.
>

It's fine, keep it, you explained why we need it.

> > >
> > >  $(TRUNNER_BPF_LSKELS): %.lskel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
> > >         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> > > @@ -421,6 +428,7 @@ $(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_BPF_OBJS) $(BPFTOOL) | $(TRUNNER_OUTPUT)
> > >         $(Q)diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
> > >         $$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
> > >         $(Q)$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@
> > > +       $(Q)$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h)
> >
> > probably don't need subskel for LSKELS (and it just adds race when we
> > generate both skeleton and light skeleton for the same object file)
>
> We're not generating subskels for LSKELS, that's just confusing diff output.
> This is under the $(TRUNNER_BPF_SKELS_LINKED) outputs.

indeed confusing, never mind then

>
> >
> > can you please add CONFIG_BPF_SYSCALL here as well, to check that
> > externs are properly "merged" and found, even if they overlap between
> > library and app BPF code
>
> Sure.
>
> >
> > libbpf supports .data.my_custom_name and .rodata.my_custom_whatever,
> > let's have a variable to test this also works?
>
> Sure.
>
> >
> > let's move this into progs/test_subskeleton.c instead. It will
> > simulate a bit more complicated scenario, where library expects
> > application to define and provide a map, but the library itself
> > doesn't define it. It should work just fine right now (I think), but
> > just in case let's double check that having only "extern map" in the
> > library works.
>
> This fails to even open in bpftool:
>
> libbpf: map 'map2': unsupported map linkage extern.
> Error: failed to open BPF object file: Operation not supported
>
> If we think this is valuable enough to support, let's tackle it separately after
> the bulk of this functionality is merged?

yep, totally. It's not super critical to support, but seems like a
useful use case for library to be able to access some pre-agreed map
in the final BPF app

>
>
> -- Delyan
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index a7eead8820a0..595565eb68c0 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -31,6 +31,7 @@  test_tcp_check_syncookie_user
 test_sysctl
 xdping
 test_cpp
+*.subskel.h
 *.skel.h
 *.lskel.h
 /no_alu32
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index fe12b4f5fe20..9f7b22faedd6 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -328,6 +328,12 @@  SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
 LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h		\
 		linked_vars.skel.h linked_maps.skel.h
 
+# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
+# but that's created as a side-effect of the skel.h generation.
+LINKED_SKELS += test_subskeleton.skel.h test_subskeleton_lib.skel.h
+test_subskeleton.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o test_subskeleton.o
+test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o
+
 LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \
 	test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c \
 	map_ptr_kern.c core_kern.c core_kern_overflow.c
@@ -404,6 +410,7 @@  $(TRUNNER_BPF_SKELS): %.skel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$(Q)$$(BPFTOOL) gen object $$(<:.o=.linked3.o) $$(<:.o=.linked2.o)
 	$(Q)diff $$(<:.o=.linked2.o) $$(<:.o=.linked3.o)
 	$(Q)$$(BPFTOOL) gen skeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.o=)) > $$@
+	$(Q)$$(BPFTOOL) gen subskeleton $$(<:.o=.linked3.o) name $$(notdir $$(<:.o=)) > $$(@:.skel.h=.subskel.h)
 
 $(TRUNNER_BPF_LSKELS): %.lskel.h: %.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
@@ -421,6 +428,7 @@  $(TRUNNER_BPF_SKELS_LINKED): $(TRUNNER_BPF_OBJS) $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$(Q)diff $$(@:.skel.h=.linked2.o) $$(@:.skel.h=.linked3.o)
 	$$(call msg,GEN-SKEL,$(TRUNNER_BINARY),$$@)
 	$(Q)$$(BPFTOOL) gen skeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$@
+	$(Q)$$(BPFTOOL) gen subskeleton $$(@:.skel.h=.linked3.o) name $$(notdir $$(@:.skel.h=)) > $$(@:.skel.h=.subskel.h)
 endif
 
 # ensure we set up tests.h header generation rule just once
@@ -557,6 +565,6 @@  $(OUTPUT)/bench: $(OUTPUT)/bench.o \
 EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(SCRATCH_DIR) $(HOST_SCRATCH_DIR)	\
 	prog_tests/tests.h map_tests/tests.h verifier/tests.h		\
 	feature bpftool							\
-	$(addprefix $(OUTPUT)/,*.o *.skel.h *.lskel.h no_alu32 bpf_gcc bpf_testmod.ko)
+	$(addprefix $(OUTPUT)/,*.o *.skel.h *.lskel.h *.subskel.h no_alu32 bpf_gcc bpf_testmod.ko)
 
 .PHONY: docs docs-clean
diff --git a/tools/testing/selftests/bpf/prog_tests/subskeleton.c b/tools/testing/selftests/bpf/prog_tests/subskeleton.c
new file mode 100644
index 000000000000..9cbe17281f17
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/subskeleton.c
@@ -0,0 +1,83 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include "test_subskeleton.skel.h"
+#include "test_subskeleton_lib.subskel.h"
+
+void subskeleton_lib_setup(struct bpf_object *obj)
+{
+	struct test_subskeleton_lib *lib = test_subskeleton_lib__open(obj);
+
+	if (!ASSERT_OK_PTR(lib, "open subskeleton"))
+		goto out;
+
+	*lib->rodata.var1 = 1;
+	*lib->data.var2 = 2;
+	lib->bss.var3->var3_1 = 3;
+	lib->bss.var3->var3_2 = 4;
+
+out:
+	test_subskeleton_lib__destroy(lib);
+}
+
+int subskeleton_lib_subresult(struct bpf_object *obj)
+{
+	struct test_subskeleton_lib *lib = test_subskeleton_lib__open(obj);
+	int result;
+
+	if (!ASSERT_OK_PTR(lib, "open subskeleton")) {
+		result = -EINVAL;
+		goto out;
+	}
+
+	result = *lib->bss.libout1;
+	ASSERT_EQ(result, 1 + 2 + 3 + 4 + 5 + 6, "lib subresult");
+
+	ASSERT_OK_PTR(lib->progs.lib_perf_handler, "lib_perf_handler");
+	ASSERT_STREQ(bpf_program__name(lib->progs.lib_perf_handler),
+		     "lib_perf_handler", "program name");
+
+	ASSERT_OK_PTR(lib->maps.map1, "map1");
+	ASSERT_STREQ(bpf_map__name(lib->maps.map1), "map1", "map name");
+
+	ASSERT_EQ(*lib->data.var5, 5, "__weak var5");
+	ASSERT_EQ(*lib->data.var6, 6, "extern var6");
+	ASSERT_TRUE(*lib->kconfig.CONFIG_BPF_SYSCALL, "CONFIG_BPF_SYSCALL");
+
+out:
+	test_subskeleton_lib__destroy(lib);
+	return result;
+}
+
+void test_subskeleton(void)
+{
+	int err, result;
+	struct test_subskeleton *skel;
+
+	skel = test_subskeleton__open();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		return;
+
+	skel->rodata->rovar1 = 10;
+	skel->rodata->var1 = 1;
+	subskeleton_lib_setup(skel->obj);
+
+	err = test_subskeleton__load(skel);
+	if (!ASSERT_OK(err, "skel_load"))
+		goto cleanup;
+
+
+	err = test_subskeleton__attach(skel);
+	if (!ASSERT_OK(err, "skel_attach"))
+		goto cleanup;
+
+	/* trigger tracepoint */
+	usleep(1);
+
+	result = subskeleton_lib_subresult(skel->obj) * 10;
+	ASSERT_EQ(skel->bss->out1, result, "unexpected calculation");
+
+cleanup:
+	test_subskeleton__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton.c b/tools/testing/selftests/bpf/progs/test_subskeleton.c
new file mode 100644
index 000000000000..5bd5452b41cd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_subskeleton.c
@@ -0,0 +1,23 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* volatile to force a read, compiler may assume 0 otherwise */
+const volatile int rovar1;
+int out1;
+
+/* Override weak symbol in test_subskeleton_lib */
+int var5 = 5;
+
+extern int lib_routine(void);
+
+SEC("raw_tp/sys_enter")
+int handler1(const void *ctx)
+{
+	out1 = lib_routine() * rovar1;
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c b/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
new file mode 100644
index 000000000000..665338006e33
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_subskeleton_lib.c
@@ -0,0 +1,56 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) Meta Platforms, Inc. and affiliates. */
+
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* volatile to force a read */
+const volatile int var1;
+volatile int var2 = 1;
+struct {
+	int var3_1;
+	__s64 var3_2;
+} var3;
+int libout1;
+
+extern volatile bool CONFIG_BPF_SYSCALL __kconfig;
+
+int var4[4];
+
+__weak int var5 SEC(".data");
+extern int var6;
+int (*fn_ptr)(void);
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 16);
+} map1 SEC(".maps");
+
+extern struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 16);
+} map2 SEC(".maps");
+
+int lib_routine(void)
+{
+	__u32 key = 1, value = 2;
+
+	(void) CONFIG_BPF_SYSCALL;
+	bpf_map_update_elem(&map2, &key, &value, BPF_ANY);
+
+	libout1 = var1 + var2 + var3.var3_1 + var3.var3_2 + var5 + var6;
+	return libout1;
+}
+
+SEC("perf_event")
+int lib_perf_handler(struct pt_regs *ctx)
+{
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c b/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c
new file mode 100644
index 000000000000..cbff92674b76
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_subskeleton_lib2.c
@@ -0,0 +1,16 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 16);
+} map2 SEC(".maps");
+
+int var6 = 6;
+
+char LICENSE[] SEC("license") = "GPL";