]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/bpf: Add BTF fixup for __naked subprog parameter names
authorYonghong Song <yonghong.song@linux.dev>
Wed, 13 May 2026 04:51:38 +0000 (21:51 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 13 May 2026 16:27:32 +0000 (09:27 -0700)
When __naked subprogs are used in verifier tests, clang drops
parameter names from their BTF FUNC_PROTO entries. This prevents
the verifier from resolving stack argument slots by name.

Add a __btf_func_path(path) annotation that points to a separate
BTF file containing properly-named FUNC entries. The test_loader
matches FUNC entries by name, detects anonymous parameters, and
replaces the FUNC_PROTO with a new one that carries parameter
names from the custom file while preserving the original type IDs.

The custom BTF file also serves as btf_custom_path for kfunc
resolution when no separate btf_custom_path is specified.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
Link: https://lore.kernel.org/r/20260513045138.2398886-1-yonghong.song@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/progs/bpf_misc.h
tools/testing/selftests/bpf/test_loader.c

index a0d7b15a24b10a9e39f87a3d665129e1ac774d0e..9eeb5b0b63d6976b1243393d9f228275afa0b21c 100644 (file)
 #define __auxiliary            __test_tag("test_auxiliary")
 #define __auxiliary_unpriv     __test_tag("test_auxiliary_unpriv")
 #define __btf_path(path)       __test_tag("test_btf_path=" path)
+#define __btf_func_path(path)  __test_tag("test_btf_func_path=" path)
 #define __arch(arch)           __test_tag("test_arch=" arch)
 #define __arch_x86_64          __arch("X86_64")
 #define __arch_arm64           __arch("ARM64")
index ee637809a1d4edce95fb1693814a095f958ecda4..abdb9e6e371362813f339ca5f1f39e5bb029e39c 100644 (file)
@@ -63,6 +63,7 @@ struct test_spec {
        struct test_subspec priv;
        struct test_subspec unpriv;
        const char *btf_custom_path;
+       const char *btf_custom_func_path;
        int log_level;
        int prog_flags;
        int mode_mask;
@@ -590,6 +591,8 @@ static int parse_test_spec(struct test_loader *tester,
                        jit_on_next_line = true;
                } else if ((val = str_has_pfx(s, "test_btf_path="))) {
                        spec->btf_custom_path = val;
+               } else if ((val = str_has_pfx(s, "test_btf_func_path="))) {
+                       spec->btf_custom_func_path = val;
                } else if ((val = str_has_pfx(s, "test_caps_unpriv="))) {
                        err = parse_caps(val, &spec->unpriv.caps, "test caps");
                        if (err)
@@ -1175,6 +1178,123 @@ static int get_stream(int stream_id, int prog_fd, char *text, size_t text_sz)
        return ret;
 }
 
+/*
+ * Fix up the program's BTF using BTF from a separate file.
+ *
+ * For __naked subprogs, clang drops parameter names from BTF. Find FUNC
+ * entries with anonymous parameters and replace their FUNC_PROTO with the
+ * properly-named version from the custom file.
+ */
+static int fixup_btf_from_path(struct bpf_object *obj, const char *path)
+{
+       struct btf *prog_btf, *custom_btf;
+       __u32 i, j, cnt, custom_cnt;
+       int err = 0;
+
+       prog_btf = bpf_object__btf(obj);
+       if (!prog_btf)
+               return 0;
+
+       custom_btf = btf__parse(path, NULL);
+       if (!ASSERT_OK_PTR(custom_btf, "parse_custom_btf"))
+               return -EINVAL;
+
+       cnt = btf__type_cnt(prog_btf);
+       custom_cnt = btf__type_cnt(custom_btf);
+
+       /* Fix up FUNC entries with anonymous params.
+        * Save all data from prog_btf BEFORE calling btf__add_*,
+        * since those calls may reallocate the BTF data buffer
+        * and invalidate any pointers obtained from btf__type_by_id.
+        */
+       for (i = 1; i < cnt; i++) {
+               const struct btf_type *t = btf__type_by_id(prog_btf, i);
+               const struct btf_type *fp, *custom_t, *custom_fp;
+               const struct btf_param *params, *custom_params;
+               __u32 ret_type_id, vlen;
+               __u32 *prog_param_types = NULL;
+               const char *name;
+               int new_proto_id;
+
+               if (!btf_is_func(t))
+                       continue;
+
+               fp = btf__type_by_id(prog_btf, t->type);
+               if (!fp || !btf_is_func_proto(fp) || btf_vlen(fp) == 0)
+                       continue;
+
+               /* Check if any param is anonymous */
+               params = btf_params(fp);
+               if (params[0].name_off != 0)
+                       continue;
+
+               /* Find matching FUNC by name in custom BTF */
+               name = btf__name_by_offset(prog_btf, t->name_off);
+               if (!name)
+                       continue;
+
+               for (j = 1; j < custom_cnt; j++) {
+                       const char *cname;
+
+                       custom_t = btf__type_by_id(custom_btf, j);
+                       if (!btf_is_func(custom_t))
+                               continue;
+                       cname = btf__name_by_offset(custom_btf, custom_t->name_off);
+                       if (cname && strcmp(name, cname) == 0)
+                               break;
+               }
+               if (j >= custom_cnt)
+                       continue;
+
+               custom_fp = btf__type_by_id(custom_btf, custom_t->type);
+               if (!custom_fp || !btf_is_func_proto(custom_fp))
+                       continue;
+
+               vlen = btf_vlen(fp);
+               if (vlen != btf_vlen(custom_fp))
+                       continue;
+
+               /* Save data before btf__add_* calls invalidate pointers */
+               ret_type_id = fp->type;
+               prog_param_types = malloc(vlen * sizeof(*prog_param_types));
+               if (!prog_param_types) {
+                       err = -ENOMEM;
+                       break;
+               }
+               for (j = 0; j < vlen; j++)
+                       prog_param_types[j] = params[j].type;
+
+               /* Add a new FUNC_PROTO: param names from custom, types from prog */
+               new_proto_id = btf__add_func_proto(prog_btf, ret_type_id);
+               if (new_proto_id < 0) {
+                       err = new_proto_id;
+                       free(prog_param_types);
+                       break;
+               }
+
+               custom_params = btf_params(custom_fp);
+               for (j = 0; j < vlen; j++) {
+                       const char *pname;
+
+                       pname = btf__name_by_offset(custom_btf, custom_params[j].name_off);
+                       err = btf__add_func_param(prog_btf, pname ?: "", prog_param_types[j]);
+                       if (err)
+                               break;
+               }
+               free(prog_param_types);
+               if (err)
+                       break;
+
+               /* Update the FUNC to point to the new FUNC_PROTO (re-fetch
+                * since btf__add_* may have reallocated the data buffer).
+                */
+               ((struct btf_type *)btf__type_by_id(prog_btf, i))->type = new_proto_id;
+       }
+
+       btf__free(custom_btf);
+       return err;
+}
+
 /* this function is forced noinline and has short generic name to look better
  * in test_progs output (in case of a failure)
  */
@@ -1231,13 +1351,27 @@ void run_subtest(struct test_loader *tester,
                }
        }
 
-       /* Implicitly reset to NULL if next test case doesn't specify */
+       /* Implicitly reset to NULL if next test case doesn't specify.
+        * btf_custom_func_path also serves as btf_custom_path for kfunc resolution.
+        */
        open_opts->btf_custom_path = spec->btf_custom_path;
+       if (!open_opts->btf_custom_path)
+               open_opts->btf_custom_path = spec->btf_custom_func_path;
 
        tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, open_opts);
        if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
                goto subtest_cleanup;
 
+       /* Fix up __naked subprog BTF using a separate file with named params */
+       if (spec->btf_custom_func_path) {
+               err = fixup_btf_from_path(tobj, spec->btf_custom_func_path);
+               if (err) {
+                       PRINT_FAIL("failed to fixup BTF from %s: %d\n",
+                                  spec->btf_custom_func_path, err);
+                       goto tobj_cleanup;
+               }
+       }
+
        i = 0;
        bpf_object__for_each_program(tprog_iter, tobj) {
                spec_iter = &specs[i++];