The added test cases cover the following scenarios:

  - Valid module: ensure loading succeeds when the required module BTF is
    specified.
  - Non-existent module: ensure loading fails when only a non-existent
    module is specified and the required module BTF is unavailable.
  - Duplicate module names: ensure duplicate entries are ignored.
  - Unneeded module: ensure a loaded module whose BTF is not requested is
    skipped.
  - Missing count: ensure opening fails when kmod_btf_names is provided
    without kmod_btf_names_cnt.

Signed-off-by: Fuyu Zhao <[email protected]>
---
 .../selftests/bpf/prog_tests/kmod_btfs.c      | 196 ++++++++++++++++++
 tools/testing/selftests/bpf/progs/kmod_btfs.c |  13 ++
 2 files changed, 209 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs.c

diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c 
b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
new file mode 100644
index 000000000000..61d2845a731f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "kmod_btfs.skel.h"
+
+static bool btf_skipped;
+static bool dup_ignored;
+
+static const char btf_skip_fmt[] =
+       "libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
+static const char dup_ignore_fmt[] =
+       "libbpf: duplicate kmod BTF name '%s' ignored\n";
+
+static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
+                          va_list args)
+{
+       if (!strcmp(fmt, btf_skip_fmt)) {
+               if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
+                       btf_skipped = true;
+       } else if (!strcmp(fmt, dup_ignore_fmt)) {
+               if (!strcmp(va_arg(args, char *), "bpf_testmod"))
+                       dup_ignored = true;
+       }
+
+       return 0;
+}
+
+static void kmod_btfs_pass(void)
+{
+       struct kmod_btfs *skel = NULL;
+       int ret;
+       static const char *kmods[] = { "bpf_testmod" };
+
+       DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+               .kmod_btf_names = kmods,
+               .kmod_btf_names_cnt = 1,
+       );
+
+       skel = kmod_btfs__open_opts(&opts);
+       if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+               goto out;
+
+       ret = kmod_btfs__load(skel);
+       ASSERT_OK(ret, "kmod_btfs__load");
+out:
+       kmod_btfs__destroy(skel);
+}
+
+static void kmod_btfs_nonexist(void)
+{
+       struct kmod_btfs *skel = NULL;
+       int ret;
+       static const char *nonexist_kmods[] = { "module_nonexist" };
+
+       DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+               .kmod_btf_names = nonexist_kmods,
+               .kmod_btf_names_cnt = 1,
+       );
+
+       /*
+        * Only "module_nonexist" is requested; since it does not exist in
+        * the kernel, no module BTF gets loaded. The BPF program then
+        * fails to load because the real needed module's BTF is unavailable.
+        */
+       skel = kmod_btfs__open_opts(&opts);
+       if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+               goto out;
+
+       ret = kmod_btfs__load(skel);
+       ASSERT_ERR(ret, "kmod_btfs__load");
+
+out:
+       kmod_btfs__destroy(skel);
+}
+
+static void kmod_btfs_dup(void)
+{
+       struct kmod_btfs *skel = NULL;
+       libbpf_print_fn_t old_print_cb;
+       int ret;
+       static const char *dup_kmods[] = { "bpf_testmod", "bpf_testmod" };
+
+       /* duplicate entry on purpose: libbpf should dedupe it with a warning */
+       DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+               .kmod_btf_names = dup_kmods,
+               .kmod_btf_names_cnt = 2,
+       );
+
+       dup_ignored = false;
+       old_print_cb = libbpf_set_print(libbpf_print_cb);
+
+       skel = kmod_btfs__open_opts(&opts);
+       if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+               goto out;
+
+       ASSERT_TRUE(dup_ignored, "dup_ignored");
+
+       ret = kmod_btfs__load(skel);
+       ASSERT_OK(ret, "kmod_btfs__load");
+
+       kmod_btfs__destroy(skel);
+out:
+       libbpf_set_print(old_print_cb);
+}
+
+static void kmod_btfs_skip(void)
+{
+       struct kmod_btfs *skel = NULL;
+       libbpf_print_fn_t old_print_cb;
+       bool testmod_unloaded = false;
+       int ret;
+       static const char *kmods[] = { "bpf_testmod" };
+
+       DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+               .kmod_btf_names = kmods,
+               .kmod_btf_names_cnt = 1,
+       );
+
+       btf_skipped = false;
+       old_print_cb = libbpf_set_print(libbpf_print_cb);
+
+       /*
+        * Reload bpf_testmod after bpf_test_no_cfi to ensure the unneeded
+        * module's BTF is enumerated first and skipped.
+        */
+       if (!ASSERT_OK(unload_bpf_testmod(false), "unload bpf_testmod"))
+               goto cleanup_print;
+       testmod_unloaded = true;
+
+       if (!ASSERT_OK(load_module("bpf_test_no_cfi.ko", false),
+                      "load unneeded bpf_test_no_cfi module"))
+               goto cleanup_print;
+       if (!ASSERT_OK(load_bpf_testmod(false), "restore bpf_testmod"))
+               goto cleanup_module;
+       testmod_unloaded = false;
+
+       skel = kmod_btfs__open_opts(&opts);
+       if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+               goto cleanup_module;
+
+       ret = kmod_btfs__load(skel);
+       if (!ASSERT_OK(ret, "kmod_btfs__load"))
+               goto cleanup_skel;
+
+       ASSERT_TRUE(btf_skipped, "btf_skipped");
+
+cleanup_skel:
+       kmod_btfs__destroy(skel);
+cleanup_module:
+       ASSERT_OK(unload_module("bpf_test_no_cfi", false),
+                 "unload unneeded bpf_test_no_cfi module");
+cleanup_print:
+       if (testmod_unloaded)
+               ASSERT_OK(load_bpf_testmod(false),
+                         "restore bpf_testmod on error path");
+       libbpf_set_print(old_print_cb);
+}
+
+static void kmod_btfs_no_cnt(void)
+{
+       struct kmod_btfs *skel = NULL;
+       static const char *kmods[] = { "bpf_testmod" };
+
+       /*
+        * kmod_btf_names set but kmod_btf_names_cnt not set:
+        * open must fail with -EINVAL.
+        */
+       DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+               .kmod_btf_names = kmods,
+       );
+
+       skel = kmod_btfs__open_opts(&opts);
+       ASSERT_NULL(skel, "kmod_btfs__open_opts without kmod_btf_names_cnt");
+}
+
+void serial_test_kmod_btfs(void)
+{
+       if (!env.has_testmod) {
+               test__skip();
+               return;
+       }
+
+       if (test__start_subtest("kmod_btfs_pass"))
+               kmod_btfs_pass();
+
+       if (test__start_subtest("kmod_btfs_nonexist"))
+               kmod_btfs_nonexist();
+
+       if (test__start_subtest("kmod_btfs_dup"))
+               kmod_btfs_dup();
+
+       if (test__start_subtest("kmod_btfs_skip"))
+               kmod_btfs_skip();
+
+       if (test__start_subtest("kmod_btfs_no_cnt"))
+               kmod_btfs_no_cnt();
+}
diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs.c 
b/tools/testing/selftests/bpf/progs/kmod_btfs.c
new file mode 100644
index 000000000000..b96860d60e0d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kmod_btfs.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry/bpf_testmod_loop_test")
+int BPF_PROG(test_kmod_btfs)
+{
+       return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.34.1


Reply via email to