selftests/bpf: Add notion of auxiliary programs for test_loader
authorEduard Zingerman <eddyz87@gmail.com>
Fri, 21 Apr 2023 17:42:11 +0000 (20:42 +0300)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 21 Apr 2023 19:16:56 +0000 (12:16 -0700)
In order to express test cases that use bpf_tail_call() intrinsic it
is necessary to have several programs to be loaded at a time.
This commit adds __auxiliary annotation to the set of annotations
supported by test_loader.c. Programs marked as auxiliary are always
loaded but are not treated as a separate test.

For example:

    void dummy_prog1(void);

    struct {
            __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
            __uint(max_entries, 4);
            __uint(key_size, sizeof(int));
            __array(values, void (void));
    } prog_map SEC(".maps") = {
            .values = {
                    [0] = (void *) &dummy_prog1,
            },
    };

    SEC("tc")
    __auxiliary
    __naked void dummy_prog1(void) {
            asm volatile ("r0 = 42; exit;");
    }

    SEC("tc")
    __description("reference tracking: check reference or tail call")
    __success __retval(0)
    __naked void check_reference_or_tail_call(void)
    {
            asm volatile (
            "r2 = %[prog_map] ll;"
            "r3 = 0;"
            "call %[bpf_tail_call];"
            "r0 = 0;"
            "exit;"
            :: __imm(bpf_tail_call),
            :  __clobber_all);
    }

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20230421174234.2391278-2-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/progs/bpf_misc.h
tools/testing/selftests/bpf/test_loader.c

index 3b307de..d3c1217 100644 (file)
  *                   - A numeric value.
  *                   Multiple __flag attributes could be specified, the final flags
  *                   value is derived by applying binary "or" to all specified values.
+ *
+ * __auxiliary         Annotated program is not a separate test, but used as auxiliary
+ *                     for some other test cases and should always be loaded.
+ * __auxiliary_unpriv  Same, but load program in unprivileged mode.
  */
 #define __msg(msg)             __attribute__((btf_decl_tag("comment:test_expect_msg=" msg)))
 #define __failure              __attribute__((btf_decl_tag("comment:test_expect_failure")))
@@ -65,6 +69,8 @@
 #define __flag(flag)           __attribute__((btf_decl_tag("comment:test_prog_flags="#flag)))
 #define __retval(val)          __attribute__((btf_decl_tag("comment:test_retval="#val)))
 #define __retval_unpriv(val)   __attribute__((btf_decl_tag("comment:test_retval_unpriv="#val)))
+#define __auxiliary            __attribute__((btf_decl_tag("comment:test_auxiliary")))
+#define __auxiliary_unpriv     __attribute__((btf_decl_tag("comment:test_auxiliary_unpriv")))
 
 /* Convenience macro for use with 'asm volatile' blocks */
 #define __naked __attribute__((naked))
index 40c9b7d..b4edd84 100644 (file)
@@ -25,6 +25,8 @@
 #define TEST_TAG_DESCRIPTION_PFX "comment:test_description="
 #define TEST_TAG_RETVAL_PFX "comment:test_retval="
 #define TEST_TAG_RETVAL_PFX_UNPRIV "comment:test_retval_unpriv="
+#define TEST_TAG_AUXILIARY "comment:test_auxiliary"
+#define TEST_TAG_AUXILIARY_UNPRIV "comment:test_auxiliary_unpriv"
 
 /* Warning: duplicated in bpf_misc.h */
 #define POINTER_VALUE  0xcafe4all
@@ -59,6 +61,8 @@ struct test_spec {
        int log_level;
        int prog_flags;
        int mode_mask;
+       bool auxiliary;
+       bool valid;
 };
 
 static int tester_init(struct test_loader *tester)
@@ -87,6 +91,11 @@ static void free_test_spec(struct test_spec *spec)
        free(spec->unpriv.name);
        free(spec->priv.expect_msgs);
        free(spec->unpriv.expect_msgs);
+
+       spec->priv.name = NULL;
+       spec->unpriv.name = NULL;
+       spec->priv.expect_msgs = NULL;
+       spec->unpriv.expect_msgs = NULL;
 }
 
 static int push_msg(const char *msg, struct test_subspec *subspec)
@@ -204,6 +213,12 @@ static int parse_test_spec(struct test_loader *tester,
                        spec->unpriv.expect_failure = false;
                        spec->mode_mask |= UNPRIV;
                        has_unpriv_result = true;
+               } else if (strcmp(s, TEST_TAG_AUXILIARY) == 0) {
+                       spec->auxiliary = true;
+                       spec->mode_mask |= PRIV;
+               } else if (strcmp(s, TEST_TAG_AUXILIARY_UNPRIV) == 0) {
+                       spec->auxiliary = true;
+                       spec->mode_mask |= UNPRIV;
                } else if (str_has_pfx(s, TEST_TAG_EXPECT_MSG_PFX)) {
                        msg = s + sizeof(TEST_TAG_EXPECT_MSG_PFX) - 1;
                        err = push_msg(msg, &spec->priv);
@@ -314,6 +329,8 @@ static int parse_test_spec(struct test_loader *tester,
                }
        }
 
+       spec->valid = true;
+
        return 0;
 
 cleanup:
@@ -516,16 +533,18 @@ void run_subtest(struct test_loader *tester,
                 struct bpf_object_open_opts *open_opts,
                 const void *obj_bytes,
                 size_t obj_byte_cnt,
+                struct test_spec *specs,
                 struct test_spec *spec,
                 bool unpriv)
 {
        struct test_subspec *subspec = unpriv ? &spec->unpriv : &spec->priv;
+       struct bpf_program *tprog, *tprog_iter;
+       struct test_spec *spec_iter;
        struct cap_state caps = {};
-       struct bpf_program *tprog;
        struct bpf_object *tobj;
        struct bpf_map *map;
-       int retval;
-       int err;
+       int retval, err, i;
+       bool should_load;
 
        if (!test__start_subtest(subspec->name))
                return;
@@ -546,15 +565,23 @@ void run_subtest(struct test_loader *tester,
        if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
                goto subtest_cleanup;
 
-       bpf_object__for_each_program(tprog, tobj)
-               bpf_program__set_autoload(tprog, false);
+       i = 0;
+       bpf_object__for_each_program(tprog_iter, tobj) {
+               spec_iter = &specs[i++];
+               should_load = false;
+
+               if (spec_iter->valid) {
+                       if (strcmp(bpf_program__name(tprog_iter), spec->prog_name) == 0) {
+                               tprog = tprog_iter;
+                               should_load = true;
+                       }
 
-       bpf_object__for_each_program(tprog, tobj) {
-               /* only load specified program */
-               if (strcmp(bpf_program__name(tprog), spec->prog_name) == 0) {
-                       bpf_program__set_autoload(tprog, true);
-                       break;
+                       if (spec_iter->auxiliary &&
+                           spec_iter->mode_mask & (unpriv ? UNPRIV : PRIV))
+                               should_load = true;
                }
+
+               bpf_program__set_autoload(tprog_iter, should_load);
        }
 
        prepare_case(tester, spec, tobj, tprog);
@@ -617,11 +644,12 @@ static void process_subtest(struct test_loader *tester,
                            skel_elf_bytes_fn elf_bytes_factory)
 {
        LIBBPF_OPTS(bpf_object_open_opts, open_opts, .object_name = skel_name);
+       struct test_spec *specs = NULL;
        struct bpf_object *obj = NULL;
        struct bpf_program *prog;
        const void *obj_bytes;
+       int err, i, nr_progs;
        size_t obj_byte_cnt;
-       int err;
 
        if (tester_init(tester) < 0)
                return; /* failed to initialize tester */
@@ -631,25 +659,42 @@ static void process_subtest(struct test_loader *tester,
        if (!ASSERT_OK_PTR(obj, "obj_open_mem"))
                return;
 
-       bpf_object__for_each_program(prog, obj) {
-               struct test_spec spec;
+       nr_progs = 0;
+       bpf_object__for_each_program(prog, obj)
+               ++nr_progs;
+
+       specs = calloc(nr_progs, sizeof(struct test_spec));
+       if (!ASSERT_OK_PTR(specs, "Can't alloc specs array"))
+               return;
 
-               /* if we can't derive test specification, go to the next test */
-               err = parse_test_spec(tester, obj, prog, &spec);
-               if (err) {
+       i = 0;
+       bpf_object__for_each_program(prog, obj) {
+               /* ignore tests for which  we can't derive test specification */
+               err = parse_test_spec(tester, obj, prog, &specs[i++]);
+               if (err)
                        PRINT_FAIL("Can't parse test spec for program '%s'\n",
                                   bpf_program__name(prog));
+       }
+
+       i = 0;
+       bpf_object__for_each_program(prog, obj) {
+               struct test_spec *spec = &specs[i++];
+
+               if (!spec->valid || spec->auxiliary)
                        continue;
-               }
 
-               if (spec.mode_mask & PRIV)
-                       run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt, &spec, false);
-               if (spec.mode_mask & UNPRIV)
-                       run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt, &spec, true);
+               if (spec->mode_mask & PRIV)
+                       run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
+                                   specs, spec, false);
+               if (spec->mode_mask & UNPRIV)
+                       run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
+                                   specs, spec, true);
 
-               free_test_spec(&spec);
        }
 
+       for (i = 0; i < nr_progs; ++i)
+               free_test_spec(&specs[i]);
+       free(specs);
        bpf_object__close(obj);
 }