Merge branch 'Introduce type match support'
authorAndrii Nakryiko <andrii@kernel.org>
Wed, 6 Jul 2022 03:24:13 +0000 (20:24 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Wed, 6 Jul 2022 04:15:20 +0000 (21:15 -0700)
Daniel Müller says:

====================

This patch set proposes the addition of a new way for performing type queries to
BPF. It introduces the "type matches" relation, similar to what is already
present with "type exists" (in the form of bpf_core_type_exists).

"type exists" performs fairly superficial checking, mostly concerned with
whether a type exists in the kernel and is of the same kind (enum/struct/...).
Notably, compatibility checks for members of composite types is lacking.

The newly introduced "type matches" (bpf_core_type_matches) fills this gap in
that it performs stricter checks: compatibility of members and existence of
similarly named enum variants is checked as well. E.g., given these definitions:

struct task_struct___og { int pid; int tgid; };

struct task_struct___foo { int foo; }

'task_struct___og' would "match" the kernel type 'task_struct', because the
members match up, while 'task_struct___foo' would not match, because the
kernel's 'task_struct' has no member named 'foo'.

More precisely, the "type match" relation is defined as follows (copied from
source):
- modifiers and typedefs are stripped (and, hence, effectively ignored)
- generally speaking types need to be of same kind (struct vs. struct, union
  vs. union, etc.)
  - exceptions are struct/union behind a pointer which could also match a
    forward declaration of a struct or union, respectively, and enum vs.
    enum64 (see below)
Then, depending on type:
- integers:
  - match if size and signedness match
- arrays & pointers:
  - target types are recursively matched
- structs & unions:
  - local members need to exist in target with the same name
  - for each member we recursively check match unless it is already behind a
    pointer, in which case we only check matching names and compatible kind
- enums:
  - local variants have to have a match in target by symbolic name (but not
    numeric value)
  - size has to match (but enum may match enum64 and vice versa)
- function pointers:
  - number and position of arguments in local type has to match target
  - for each argument and the return value we recursively check match

Enabling this feature requires a new relocation to be made known to the
compiler. This is being taken care of for LLVM as part of
https://reviews.llvm.org/D126838.

If applied, among other things, usage of this functionality could have helped
flag issues such as the one discussed here
https://lore.kernel.org/all/93a20759600c05b6d9e4359a1517c88e06b44834.camel@fb.com/
earlier.

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
---
Changelog:
v2 -> v3:
- renamed btfgen_mark_types_match
- covered BTF_KIND_RESTRICT in type match marking logic
- used bpf_core_names_match in more places
- reworked "behind pointer" logic
- added test using live task_struct

v1 -> v2:
- deduplicated and moved core algorithm into relo_core.c
- adjusted bpf_core_names_match to get btf_type passed in
- removed some length equality checks before strncmp usage
- correctly use kflag from targ_t instead of local_t
- added comment for meaning of kflag w/ FWD kind
- __u32 -> u32
- handle BTF_KIND_FWD properly in bpftool marking logic
- rebased
====================

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
13 files changed:
include/uapi/linux/bpf.h
kernel/bpf/btf.c
tools/bpf/bpftool/gen.c
tools/include/uapi/linux/bpf.h
tools/lib/bpf/bpf_core_read.h
tools/lib/bpf/libbpf.c
tools/lib/bpf/relo_core.c
tools/lib/bpf/relo_core.h
tools/testing/selftests/bpf/prog_tests/core_reloc.c
tools/testing/selftests/bpf/progs/btf__core_reloc_type_based___diff.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/core_reloc_types.h
tools/testing/selftests/bpf/progs/test_core_reloc_kernel.c
tools/testing/selftests/bpf/progs/test_core_reloc_type_based.c

index ad9e731..379e68f 100644 (file)
@@ -6786,6 +6786,7 @@ enum bpf_core_relo_kind {
        BPF_CORE_TYPE_SIZE = 9,              /* type size in bytes */
        BPF_CORE_ENUMVAL_EXISTS = 10,        /* enum value existence in target kernel */
        BPF_CORE_ENUMVAL_VALUE = 11,         /* enum value integer value */
+       BPF_CORE_TYPE_MATCHES = 12,          /* type match in target kernel */
 };
 
 /*
index 8d3c7ab..4f2408a 100644 (file)
@@ -7443,6 +7443,15 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                                           MAX_TYPES_ARE_COMPAT_DEPTH);
 }
 
+#define MAX_TYPES_MATCH_DEPTH 2
+
+int bpf_core_types_match(const struct btf *local_btf, u32 local_id,
+                        const struct btf *targ_btf, u32 targ_id)
+{
+       return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false,
+                                     MAX_TYPES_MATCH_DEPTH);
+}
+
 static bool bpf_core_is_flavor_sep(const char *s)
 {
        /* check X___Y name pattern, where X and Y are not underscores */
index 480cbd8..3d35fbc 100644 (file)
@@ -1856,6 +1856,112 @@ static int btfgen_record_field_relo(struct btfgen_info *info, struct bpf_core_sp
        return 0;
 }
 
+/* Mark types, members, and member types. Compared to btfgen_record_field_relo,
+ * this function does not rely on the target spec for inferring members, but
+ * uses the associated BTF.
+ *
+ * The `behind_ptr` argument is used to stop marking of composite types reached
+ * through a pointer. This way, we can keep BTF size in check while providing
+ * reasonable match semantics.
+ */
+static int btfgen_mark_type_match(struct btfgen_info *info, __u32 type_id, bool behind_ptr)
+{
+       const struct btf_type *btf_type;
+       struct btf *btf = info->src_btf;
+       struct btf_type *cloned_type;
+       int i, err;
+
+       if (type_id == 0)
+               return 0;
+
+       btf_type = btf__type_by_id(btf, type_id);
+       /* mark type on cloned BTF as used */
+       cloned_type = (struct btf_type *)btf__type_by_id(info->marked_btf, type_id);
+       cloned_type->name_off = MARKED;
+
+       switch (btf_kind(btf_type)) {
+       case BTF_KIND_UNKN:
+       case BTF_KIND_INT:
+       case BTF_KIND_FLOAT:
+       case BTF_KIND_ENUM:
+       case BTF_KIND_ENUM64:
+               break;
+       case BTF_KIND_STRUCT:
+       case BTF_KIND_UNION: {
+               struct btf_member *m = btf_members(btf_type);
+               __u16 vlen = btf_vlen(btf_type);
+
+               if (behind_ptr)
+                       break;
+
+               for (i = 0; i < vlen; i++, m++) {
+                       /* mark member */
+                       btfgen_mark_member(info, type_id, i);
+
+                       /* mark member's type */
+                       err = btfgen_mark_type_match(info, m->type, false);
+                       if (err)
+                               return err;
+               }
+               break;
+       }
+       case BTF_KIND_CONST:
+       case BTF_KIND_FWD:
+       case BTF_KIND_RESTRICT:
+       case BTF_KIND_TYPEDEF:
+       case BTF_KIND_VOLATILE:
+               return btfgen_mark_type_match(info, btf_type->type, behind_ptr);
+       case BTF_KIND_PTR:
+               return btfgen_mark_type_match(info, btf_type->type, true);
+       case BTF_KIND_ARRAY: {
+               struct btf_array *array;
+
+               array = btf_array(btf_type);
+               /* mark array type */
+               err = btfgen_mark_type_match(info, array->type, false);
+               /* mark array's index type */
+               err = err ? : btfgen_mark_type_match(info, array->index_type, false);
+               if (err)
+                       return err;
+               break;
+       }
+       case BTF_KIND_FUNC_PROTO: {
+               __u16 vlen = btf_vlen(btf_type);
+               struct btf_param *param;
+
+               /* mark ret type */
+               err = btfgen_mark_type_match(info, btf_type->type, false);
+               if (err)
+                       return err;
+
+               /* mark parameters types */
+               param = btf_params(btf_type);
+               for (i = 0; i < vlen; i++) {
+                       err = btfgen_mark_type_match(info, param->type, false);
+                       if (err)
+                               return err;
+                       param++;
+               }
+               break;
+       }
+       /* tells if some other type needs to be handled */
+       default:
+               p_err("unsupported kind: %s (%d)", btf_kind_str(btf_type), type_id);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* Mark types, members, and member types. Compared to btfgen_record_field_relo,
+ * this function does not rely on the target spec for inferring members, but
+ * uses the associated BTF.
+ */
+static int btfgen_record_type_match_relo(struct btfgen_info *info, struct bpf_core_spec *targ_spec)
+{
+       return btfgen_mark_type_match(info, targ_spec->root_type_id, false);
+}
+
 static int btfgen_record_type_relo(struct btfgen_info *info, struct bpf_core_spec *targ_spec)
 {
        return btfgen_mark_type(info, targ_spec->root_type_id, true);
@@ -1882,6 +1988,8 @@ static int btfgen_record_reloc(struct btfgen_info *info, struct bpf_core_spec *r
        case BPF_CORE_TYPE_EXISTS:
        case BPF_CORE_TYPE_SIZE:
                return btfgen_record_type_relo(info, res);
+       case BPF_CORE_TYPE_MATCHES:
+               return btfgen_record_type_match_relo(info, res);
        case BPF_CORE_ENUMVAL_EXISTS:
        case BPF_CORE_ENUMVAL_VALUE:
                return btfgen_record_enumval_relo(info, res);
index ad9e731..379e68f 100644 (file)
@@ -6786,6 +6786,7 @@ enum bpf_core_relo_kind {
        BPF_CORE_TYPE_SIZE = 9,              /* type size in bytes */
        BPF_CORE_ENUMVAL_EXISTS = 10,        /* enum value existence in target kernel */
        BPF_CORE_ENUMVAL_VALUE = 11,         /* enum value integer value */
+       BPF_CORE_TYPE_MATCHES = 12,          /* type match in target kernel */
 };
 
 /*
index fd48b1f..496e6a8 100644 (file)
@@ -29,6 +29,7 @@ enum bpf_type_id_kind {
 enum bpf_type_info_kind {
        BPF_TYPE_EXISTS = 0,            /* type existence in target kernel */
        BPF_TYPE_SIZE = 1,              /* type size in target kernel */
+       BPF_TYPE_MATCHES = 2,           /* type match in target kernel */
 };
 
 /* second argument to __builtin_preserve_enum_value() built-in */
@@ -183,6 +184,16 @@ enum bpf_enum_value_kind {
 #define bpf_core_type_exists(type)                                         \
        __builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
 
+/*
+ * Convenience macro to check that provided named type
+ * (struct/union/enum/typedef) "matches" that in a target kernel.
+ * Returns:
+ *    1, if the type matches in the target kernel's BTF;
+ *    0, if the type does not match any in the target kernel
+ */
+#define bpf_core_type_matches(type)                                        \
+       __builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_MATCHES)
+
 /*
  * Convenience macro to get the byte size of a provided named type
  * (struct/union/enum/typedef) in a target kernel.
index 8a45a84..64c4cc6 100644 (file)
@@ -5470,6 +5470,12 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
        return __bpf_core_types_are_compat(local_btf, local_id, targ_btf, targ_id, 32);
 }
 
+int bpf_core_types_match(const struct btf *local_btf, __u32 local_id,
+                        const struct btf *targ_btf, __u32 targ_id)
+{
+       return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false, 32);
+}
+
 static size_t bpf_core_hash_fn(const void *key, void *ctx)
 {
        return (size_t)key;
index e070123..fe25330 100644 (file)
@@ -95,6 +95,7 @@ static const char *core_relo_kind_str(enum bpf_core_relo_kind kind)
        case BPF_CORE_TYPE_ID_LOCAL: return "local_type_id";
        case BPF_CORE_TYPE_ID_TARGET: return "target_type_id";
        case BPF_CORE_TYPE_EXISTS: return "type_exists";
+       case BPF_CORE_TYPE_MATCHES: return "type_matches";
        case BPF_CORE_TYPE_SIZE: return "type_size";
        case BPF_CORE_ENUMVAL_EXISTS: return "enumval_exists";
        case BPF_CORE_ENUMVAL_VALUE: return "enumval_value";
@@ -123,6 +124,7 @@ static bool core_relo_is_type_based(enum bpf_core_relo_kind kind)
        case BPF_CORE_TYPE_ID_LOCAL:
        case BPF_CORE_TYPE_ID_TARGET:
        case BPF_CORE_TYPE_EXISTS:
+       case BPF_CORE_TYPE_MATCHES:
        case BPF_CORE_TYPE_SIZE:
                return true;
        default:
@@ -251,7 +253,7 @@ recur:
  *   - field 'a' access (corresponds to '2' in low-level spec);
  *   - array element #3 access (corresponds to '3' in low-level spec).
  *
- * Type-based relocations (TYPE_EXISTS/TYPE_SIZE,
+ * Type-based relocations (TYPE_EXISTS/TYPE_MATCHES/TYPE_SIZE,
  * TYPE_ID_LOCAL/TYPE_ID_TARGET) don't capture any field information. Their
  * spec and raw_spec are kept empty.
  *
@@ -568,9 +570,14 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,
        targ_spec->relo_kind = local_spec->relo_kind;
 
        if (core_relo_is_type_based(local_spec->relo_kind)) {
-               return bpf_core_types_are_compat(local_spec->btf,
-                                                local_spec->root_type_id,
-                                                targ_btf, targ_id);
+               if (local_spec->relo_kind == BPF_CORE_TYPE_MATCHES)
+                       return bpf_core_types_match(local_spec->btf,
+                                                   local_spec->root_type_id,
+                                                   targ_btf, targ_id);
+               else
+                       return bpf_core_types_are_compat(local_spec->btf,
+                                                        local_spec->root_type_id,
+                                                        targ_btf, targ_id);
        }
 
        local_acc = &local_spec->spec[0];
@@ -819,6 +826,7 @@ static int bpf_core_calc_type_relo(const struct bpf_core_relo *relo,
                        *validate = false;
                break;
        case BPF_CORE_TYPE_EXISTS:
+       case BPF_CORE_TYPE_MATCHES:
                *val = 1;
                break;
        case BPF_CORE_TYPE_SIZE:
@@ -1410,3 +1418,266 @@ int bpf_core_calc_relo_insn(const char *prog_name,
 
        return 0;
 }
+
+static bool bpf_core_names_match(const struct btf *local_btf, size_t local_name_off,
+                                const struct btf *targ_btf, size_t targ_name_off)
+{
+       const char *local_n, *targ_n;
+       size_t local_len, targ_len;
+
+       local_n = btf__name_by_offset(local_btf, local_name_off);
+       targ_n = btf__name_by_offset(targ_btf, targ_name_off);
+
+       if (str_is_empty(targ_n))
+               return str_is_empty(local_n);
+
+       targ_len = bpf_core_essential_name_len(targ_n);
+       local_len = bpf_core_essential_name_len(local_n);
+
+       return targ_len == local_len && strncmp(local_n, targ_n, local_len) == 0;
+}
+
+static int bpf_core_enums_match(const struct btf *local_btf, const struct btf_type *local_t,
+                               const struct btf *targ_btf, const struct btf_type *targ_t)
+{
+       __u16 local_vlen = btf_vlen(local_t);
+       __u16 targ_vlen = btf_vlen(targ_t);
+       int i, j;
+
+       if (local_t->size != targ_t->size)
+               return 0;
+
+       if (local_vlen > targ_vlen)
+               return 0;
+
+       /* iterate over the local enum's variants and make sure each has
+        * a symbolic name correspondent in the target
+        */
+       for (i = 0; i < local_vlen; i++) {
+               bool matched = false;
+               __u32 local_n_off, targ_n_off;
+
+               local_n_off = btf_is_enum(local_t) ? btf_enum(local_t)[i].name_off :
+                                                    btf_enum64(local_t)[i].name_off;
+
+               for (j = 0; j < targ_vlen; j++) {
+                       targ_n_off = btf_is_enum(targ_t) ? btf_enum(targ_t)[j].name_off :
+                                                          btf_enum64(targ_t)[j].name_off;
+
+                       if (bpf_core_names_match(local_btf, local_n_off, targ_btf, targ_n_off)) {
+                               matched = true;
+                               break;
+                       }
+               }
+
+               if (!matched)
+                       return 0;
+       }
+       return 1;
+}
+
+static int bpf_core_composites_match(const struct btf *local_btf, const struct btf_type *local_t,
+                                    const struct btf *targ_btf, const struct btf_type *targ_t,
+                                    bool behind_ptr, int level)
+{
+       const struct btf_member *local_m = btf_members(local_t);
+       __u16 local_vlen = btf_vlen(local_t);
+       __u16 targ_vlen = btf_vlen(targ_t);
+       int i, j, err;
+
+       if (local_vlen > targ_vlen)
+               return 0;
+
+       /* check that all local members have a match in the target */
+       for (i = 0; i < local_vlen; i++, local_m++) {
+               const struct btf_member *targ_m = btf_members(targ_t);
+               bool matched = false;
+
+               for (j = 0; j < targ_vlen; j++, targ_m++) {
+                       if (!bpf_core_names_match(local_btf, local_m->name_off,
+                                                 targ_btf, targ_m->name_off))
+                               continue;
+
+                       err = __bpf_core_types_match(local_btf, local_m->type, targ_btf,
+                                                    targ_m->type, behind_ptr, level - 1);
+                       if (err > 0) {
+                               matched = true;
+                               break;
+                       }
+               }
+
+               if (!matched)
+                       return 0;
+       }
+       return 1;
+}
+
+/* Check that two types "match".
+ *
+ * The matching relation is defined as follows:
+ * - modifiers and typedefs are stripped (and, hence, effectively ignored)
+ * - generally speaking types need to be of same kind (struct vs. struct, union
+ *   vs. union, etc.)
+ *   - exceptions are struct/union behind a pointer which could also match a
+ *     forward declaration of a struct or union, respectively, and enum vs.
+ *     enum64 (see below)
+ * Then, depending on type:
+ * - integers:
+ *   - match if size and signedness match
+ * - arrays & pointers:
+ *   - target types are recursively matched
+ * - structs & unions:
+ *   - local members need to exist in target with the same name
+ *   - for each member we recursively check match unless it is already behind a
+ *     pointer, in which case we only check matching names and compatible kind
+ * - enums:
+ *   - local variants have to have a match in target by symbolic name (but not
+ *     numeric value)
+ *   - size has to match (but enum may match enum64 and vice versa)
+ * - function pointers:
+ *   - number and position of arguments in local type has to match target
+ *   - for each argument and the return value we recursively check match
+ */
+int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                          __u32 targ_id, bool behind_ptr, int level)
+{
+       const struct btf_type *local_t, *targ_t;
+       int depth = 32; /* max recursion depth */
+       __u16 local_k, targ_k;
+
+       if (level <= 0)
+               return -EINVAL;
+
+       local_t = btf_type_by_id(local_btf, local_id);
+       targ_t = btf_type_by_id(targ_btf, targ_id);
+
+recur:
+       depth--;
+       if (depth < 0)
+               return -EINVAL;
+
+       local_t = skip_mods_and_typedefs(local_btf, local_id, &local_id);
+       targ_t = skip_mods_and_typedefs(targ_btf, targ_id, &targ_id);
+       if (!local_t || !targ_t)
+               return -EINVAL;
+
+       if (!bpf_core_names_match(local_btf, local_t->name_off, targ_btf, targ_t->name_off))
+               return 0;
+
+       local_k = btf_kind(local_t);
+       targ_k = btf_kind(targ_t);
+
+       switch (local_k) {
+       case BTF_KIND_UNKN:
+               return local_k == targ_k;
+       case BTF_KIND_FWD: {
+               bool local_f = BTF_INFO_KFLAG(local_t->info);
+
+               if (behind_ptr) {
+                       if (local_k == targ_k)
+                               return local_f == BTF_INFO_KFLAG(targ_t->info);
+
+                       /* for forward declarations kflag dictates whether the
+                        * target is a struct (0) or union (1)
+                        */
+                       return (targ_k == BTF_KIND_STRUCT && !local_f) ||
+                              (targ_k == BTF_KIND_UNION && local_f);
+               } else {
+                       if (local_k != targ_k)
+                               return 0;
+
+                       /* match if the forward declaration is for the same kind */
+                       return local_f == BTF_INFO_KFLAG(targ_t->info);
+               }
+       }
+       case BTF_KIND_ENUM:
+       case BTF_KIND_ENUM64:
+               if (!btf_is_any_enum(targ_t))
+                       return 0;
+
+               return bpf_core_enums_match(local_btf, local_t, targ_btf, targ_t);
+       case BTF_KIND_STRUCT:
+       case BTF_KIND_UNION:
+               if (behind_ptr) {
+                       bool targ_f = BTF_INFO_KFLAG(targ_t->info);
+
+                       if (local_k == targ_k)
+                               return 1;
+
+                       if (targ_k != BTF_KIND_FWD)
+                               return 0;
+
+                       return (local_k == BTF_KIND_UNION) == targ_f;
+               } else {
+                       if (local_k != targ_k)
+                               return 0;
+
+                       return bpf_core_composites_match(local_btf, local_t, targ_btf, targ_t,
+                                                        behind_ptr, level);
+               }
+       case BTF_KIND_INT: {
+               __u8 local_sgn;
+               __u8 targ_sgn;
+
+               if (local_k != targ_k)
+                       return 0;
+
+               local_sgn = btf_int_encoding(local_t) & BTF_INT_SIGNED;
+               targ_sgn = btf_int_encoding(targ_t) & BTF_INT_SIGNED;
+
+               return local_t->size == targ_t->size && local_sgn == targ_sgn;
+       }
+       case BTF_KIND_PTR:
+               if (local_k != targ_k)
+                       return 0;
+
+               behind_ptr = true;
+
+               local_id = local_t->type;
+               targ_id = targ_t->type;
+               goto recur;
+       case BTF_KIND_ARRAY: {
+               const struct btf_array *local_array = btf_array(local_t);
+               const struct btf_array *targ_array = btf_array(targ_t);
+
+               if (local_k != targ_k)
+                       return 0;
+
+               if (local_array->nelems != targ_array->nelems)
+                       return 0;
+
+               local_id = local_array->type;
+               targ_id = targ_array->type;
+               goto recur;
+       }
+       case BTF_KIND_FUNC_PROTO: {
+               struct btf_param *local_p = btf_params(local_t);
+               struct btf_param *targ_p = btf_params(targ_t);
+               __u16 local_vlen = btf_vlen(local_t);
+               __u16 targ_vlen = btf_vlen(targ_t);
+               int i, err;
+
+               if (local_k != targ_k)
+                       return 0;
+
+               if (local_vlen != targ_vlen)
+                       return 0;
+
+               for (i = 0; i < local_vlen; i++, local_p++, targ_p++) {
+                       err = __bpf_core_types_match(local_btf, local_p->type, targ_btf,
+                                                    targ_p->type, behind_ptr, level - 1);
+                       if (err <= 0)
+                               return err;
+               }
+
+               /* tail recurse for return type check */
+               local_id = local_t->type;
+               targ_id = targ_t->type;
+               goto recur;
+       }
+       default:
+               pr_warn("unexpected kind %s relocated, local [%d], target [%d]\n",
+                       btf_kind_str(local_t), local_id, targ_id);
+               return 0;
+       }
+}
index 3fd3842..1c0566d 100644 (file)
@@ -72,6 +72,10 @@ int __bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                                const struct btf *targ_btf, __u32 targ_id, int level);
 int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                              const struct btf *targ_btf, __u32 targ_id);
+int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                          __u32 targ_id, bool behind_ptr, int level);
+int bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                        __u32 targ_id);
 
 size_t bpf_core_essential_name_len(const char *name);
 
index 2f92feb..a6f65e2 100644 (file)
@@ -543,7 +543,6 @@ static int __trigger_module_test_read(const struct core_reloc_test_case *test)
        return 0;
 }
 
-
 static const struct core_reloc_test_case test_cases[] = {
        /* validate we can find kernel image and use its BTF for relocs */
        {
@@ -556,6 +555,7 @@ static const struct core_reloc_test_case test_cases[] = {
                        .valid = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },
                        .comm = "test_progs",
                        .comm_len = sizeof("test_progs"),
+                       .local_task_struct_matches = true,
                },
                .output_len = sizeof(struct core_reloc_kernel_output),
                .raw_tp_name = "sys_enter",
@@ -752,9 +752,10 @@ static const struct core_reloc_test_case test_cases[] = {
        SIZE_CASE(size___diff_offs),
        SIZE_ERR_CASE(size___err_ambiguous),
 
-       /* validate type existence and size relocations */
+       /* validate type existence, match, and size relocations */
        TYPE_BASED_CASE(type_based, {
                .struct_exists = 1,
+               .complex_struct_exists = 1,
                .union_exists = 1,
                .enum_exists = 1,
                .typedef_named_struct_exists = 1,
@@ -765,6 +766,20 @@ static const struct core_reloc_test_case test_cases[] = {
                .typedef_void_ptr_exists = 1,
                .typedef_func_proto_exists = 1,
                .typedef_arr_exists = 1,
+
+               .struct_matches = 1,
+               .complex_struct_matches = 1,
+               .union_matches = 1,
+               .enum_matches = 1,
+               .typedef_named_struct_matches = 1,
+               .typedef_anon_struct_matches = 1,
+               .typedef_struct_ptr_matches = 1,
+               .typedef_int_matches = 1,
+               .typedef_enum_matches = 1,
+               .typedef_void_ptr_matches = 1,
+               .typedef_func_proto_matches = 1,
+               .typedef_arr_matches = 1,
+
                .struct_sz = sizeof(struct a_struct),
                .union_sz = sizeof(union a_union),
                .enum_sz = sizeof(enum an_enum),
@@ -780,6 +795,45 @@ static const struct core_reloc_test_case test_cases[] = {
        TYPE_BASED_CASE(type_based___all_missing, {
                /* all zeros */
        }),
+       TYPE_BASED_CASE(type_based___diff, {
+               .struct_exists = 1,
+               .complex_struct_exists = 1,
+               .union_exists = 1,
+               .enum_exists = 1,
+               .typedef_named_struct_exists = 1,
+               .typedef_anon_struct_exists = 1,
+               .typedef_struct_ptr_exists = 1,
+               .typedef_int_exists = 1,
+               .typedef_enum_exists = 1,
+               .typedef_void_ptr_exists = 1,
+               .typedef_func_proto_exists = 1,
+               .typedef_arr_exists = 1,
+
+               .struct_matches = 1,
+               .complex_struct_matches = 1,
+               .union_matches = 1,
+               .enum_matches = 1,
+               .typedef_named_struct_matches = 1,
+               .typedef_anon_struct_matches = 1,
+               .typedef_struct_ptr_matches = 1,
+               .typedef_int_matches = 0,
+               .typedef_enum_matches = 1,
+               .typedef_void_ptr_matches = 1,
+               .typedef_func_proto_matches = 0,
+               .typedef_arr_matches = 0,
+
+               .struct_sz = sizeof(struct a_struct___diff),
+               .union_sz = sizeof(union a_union___diff),
+               .enum_sz = sizeof(enum an_enum___diff),
+               .typedef_named_struct_sz = sizeof(named_struct_typedef___diff),
+               .typedef_anon_struct_sz = sizeof(anon_struct_typedef___diff),
+               .typedef_struct_ptr_sz = sizeof(struct_ptr_typedef___diff),
+               .typedef_int_sz = sizeof(int_typedef___diff),
+               .typedef_enum_sz = sizeof(enum_typedef___diff),
+               .typedef_void_ptr_sz = sizeof(void_ptr_typedef___diff),
+               .typedef_func_proto_sz = sizeof(func_proto_typedef___diff),
+               .typedef_arr_sz = sizeof(arr_typedef___diff),
+       }),
        TYPE_BASED_CASE(type_based___diff_sz, {
                .struct_exists = 1,
                .union_exists = 1,
@@ -792,6 +846,19 @@ static const struct core_reloc_test_case test_cases[] = {
                .typedef_void_ptr_exists = 1,
                .typedef_func_proto_exists = 1,
                .typedef_arr_exists = 1,
+
+               .struct_matches = 0,
+               .union_matches = 0,
+               .enum_matches = 0,
+               .typedef_named_struct_matches = 0,
+               .typedef_anon_struct_matches = 0,
+               .typedef_struct_ptr_matches = 1,
+               .typedef_int_matches = 0,
+               .typedef_enum_matches = 0,
+               .typedef_void_ptr_matches = 1,
+               .typedef_func_proto_matches = 0,
+               .typedef_arr_matches = 0,
+
                .struct_sz = sizeof(struct a_struct___diff_sz),
                .union_sz = sizeof(union a_union___diff_sz),
                .enum_sz = sizeof(enum an_enum___diff_sz),
@@ -806,10 +873,12 @@ static const struct core_reloc_test_case test_cases[] = {
        }),
        TYPE_BASED_CASE(type_based___incompat, {
                .enum_exists = 1,
+               .enum_matches = 1,
                .enum_sz = sizeof(enum an_enum),
        }),
        TYPE_BASED_CASE(type_based___fn_wrong_args, {
                .struct_exists = 1,
+               .struct_matches = 1,
                .struct_sz = sizeof(struct a_struct),
        }),
 
diff --git a/tools/testing/selftests/bpf/progs/btf__core_reloc_type_based___diff.c b/tools/testing/selftests/bpf/progs/btf__core_reloc_type_based___diff.c
new file mode 100644 (file)
index 0000000..57ae2c2
--- /dev/null
@@ -0,0 +1,3 @@
+#include "core_reloc_types.h"
+
+void f(struct core_reloc_type_based___diff x) {}
index 26e1033..7ef91d1 100644 (file)
@@ -13,6 +13,7 @@ struct core_reloc_kernel_output {
        int valid[10];
        char comm[sizeof("test_progs")];
        int comm_len;
+       bool local_task_struct_matches;
 };
 
 /*
@@ -860,10 +861,11 @@ struct core_reloc_size___err_ambiguous2 {
 };
 
 /*
- * TYPE EXISTENCE & SIZE
+ * TYPE EXISTENCE, MATCH & SIZE
  */
 struct core_reloc_type_based_output {
        bool struct_exists;
+       bool complex_struct_exists;
        bool union_exists;
        bool enum_exists;
        bool typedef_named_struct_exists;
@@ -875,6 +877,19 @@ struct core_reloc_type_based_output {
        bool typedef_func_proto_exists;
        bool typedef_arr_exists;
 
+       bool struct_matches;
+       bool complex_struct_matches;
+       bool union_matches;
+       bool enum_matches;
+       bool typedef_named_struct_matches;
+       bool typedef_anon_struct_matches;
+       bool typedef_struct_ptr_matches;
+       bool typedef_int_matches;
+       bool typedef_enum_matches;
+       bool typedef_void_ptr_matches;
+       bool typedef_func_proto_matches;
+       bool typedef_arr_matches;
+
        int struct_sz;
        int union_sz;
        int enum_sz;
@@ -892,6 +907,14 @@ struct a_struct {
        int x;
 };
 
+struct a_complex_struct {
+       union {
+               struct a_struct * restrict a;
+               void *b;
+       } x;
+       volatile long y;
+};
+
 union a_union {
        int y;
        int z;
@@ -923,22 +946,85 @@ typedef char arr_typedef[20];
 
 struct core_reloc_type_based {
        struct a_struct f1;
-       union a_union f2;
-       enum an_enum f3;
-       named_struct_typedef f4;
-       anon_struct_typedef f5;
-       struct_ptr_typedef f6;
-       int_typedef f7;
-       enum_typedef f8;
-       void_ptr_typedef f9;
-       func_proto_typedef f10;
-       arr_typedef f11;
+       struct a_complex_struct f2;
+       union a_union f3;
+       enum an_enum f4;
+       named_struct_typedef f5;
+       anon_struct_typedef f6;
+       struct_ptr_typedef f7;
+       int_typedef f8;
+       enum_typedef f9;
+       void_ptr_typedef f10;
+       func_proto_typedef f11;
+       arr_typedef f12;
 };
 
 /* no types in target */
 struct core_reloc_type_based___all_missing {
 };
 
+/* different member orders, enum variant values, signedness, etc */
+struct a_struct___diff {
+       int x;
+       int a;
+};
+
+struct a_struct___forward;
+
+struct a_complex_struct___diff {
+       union {
+               struct a_struct___forward *a;
+               void *b;
+       } x;
+       volatile long y;
+};
+
+union a_union___diff {
+       int z;
+       int y;
+};
+
+typedef struct a_struct___diff named_struct_typedef___diff;
+
+typedef struct { int z, x, y; } anon_struct_typedef___diff;
+
+typedef struct {
+       int c;
+       int b;
+       int a;
+} *struct_ptr_typedef___diff;
+
+enum an_enum___diff {
+       AN_ENUM_VAL2___diff = 0,
+       AN_ENUM_VAL1___diff = 42,
+       AN_ENUM_VAL3___diff = 1,
+};
+
+typedef unsigned int int_typedef___diff;
+
+typedef enum { TYPEDEF_ENUM_VAL2___diff, TYPEDEF_ENUM_VAL1___diff = 50 } enum_typedef___diff;
+
+typedef const void *void_ptr_typedef___diff;
+
+typedef int_typedef___diff (*func_proto_typedef___diff)(long);
+
+typedef char arr_typedef___diff[3];
+
+struct core_reloc_type_based___diff {
+       struct a_struct___diff f1;
+       struct a_complex_struct___diff f2;
+       union a_union___diff f3;
+       enum an_enum___diff f4;
+       named_struct_typedef___diff f5;
+       anon_struct_typedef___diff f6;
+       struct_ptr_typedef___diff f7;
+       int_typedef___diff f8;
+       enum_typedef___diff f9;
+       void_ptr_typedef___diff f10;
+       func_proto_typedef___diff f11;
+       arr_typedef___diff f12;
+};
+
 /* different type sizes, extra modifiers, anon vs named enums, etc */
 struct a_struct___diff_sz {
        long x;
index 145028b..a17dd83 100644 (file)
@@ -21,6 +21,7 @@ struct core_reloc_kernel_output {
        /* we have test_progs[-flavor], so cut flavor part */
        char comm[sizeof("test_progs")];
        int comm_len;
+       bool local_task_struct_matches;
 };
 
 struct task_struct {
@@ -30,11 +31,25 @@ struct task_struct {
        struct task_struct *group_leader;
 };
 
+struct mm_struct___wrong {
+    int abc_whatever_should_not_exist;
+};
+
+struct task_struct___local {
+    int pid;
+    struct mm_struct___wrong *mm;
+};
+
 #define CORE_READ(dst, src) bpf_core_read(dst, sizeof(*(dst)), src)
 
 SEC("raw_tracepoint/sys_enter")
 int test_core_kernel(void *ctx)
 {
+       /* Support for the BPF_TYPE_MATCHES argument to the
+        * __builtin_preserve_type_info builtin was added at some point during
+        * development of clang 15 and it's what we require for this test.
+        */
+#if __has_builtin(__builtin_preserve_type_info) && __clang_major__ >= 15
        struct task_struct *task = (void *)bpf_get_current_task();
        struct core_reloc_kernel_output *out = (void *)&data.out;
        uint64_t pid_tgid = bpf_get_current_pid_tgid();
@@ -93,6 +108,10 @@ int test_core_kernel(void *ctx)
                group_leader, group_leader, group_leader, group_leader,
                comm);
 
+       out->local_task_struct_matches = bpf_core_type_matches(struct task_struct___local);
+#else
+       data.skip = true;
+#endif
        return 0;
 }
 
index fb60f81..d95bc08 100644 (file)
@@ -19,6 +19,14 @@ struct a_struct {
        int x;
 };
 
+struct a_complex_struct {
+       union {
+               struct a_struct *a;
+               void *b;
+       } x;
+       volatile long y;
+};
+
 union a_union {
        int y;
        int z;
@@ -50,6 +58,7 @@ typedef char arr_typedef[20];
 
 struct core_reloc_type_based_output {
        bool struct_exists;
+       bool complex_struct_exists;
        bool union_exists;
        bool enum_exists;
        bool typedef_named_struct_exists;
@@ -61,6 +70,19 @@ struct core_reloc_type_based_output {
        bool typedef_func_proto_exists;
        bool typedef_arr_exists;
 
+       bool struct_matches;
+       bool complex_struct_matches;
+       bool union_matches;
+       bool enum_matches;
+       bool typedef_named_struct_matches;
+       bool typedef_anon_struct_matches;
+       bool typedef_struct_ptr_matches;
+       bool typedef_int_matches;
+       bool typedef_enum_matches;
+       bool typedef_void_ptr_matches;
+       bool typedef_func_proto_matches;
+       bool typedef_arr_matches;
+
        int struct_sz;
        int union_sz;
        int enum_sz;
@@ -77,10 +99,17 @@ struct core_reloc_type_based_output {
 SEC("raw_tracepoint/sys_enter")
 int test_core_type_based(void *ctx)
 {
-#if __has_builtin(__builtin_preserve_type_info)
+       /* Support for the BPF_TYPE_MATCHES argument to the
+        * __builtin_preserve_type_info builtin was added at some point during
+        * development of clang 15 and it's what we require for this test. Part of it
+        * could run with merely __builtin_preserve_type_info (which could be checked
+        * separately), but we have to find an upper bound.
+        */
+#if __has_builtin(__builtin_preserve_type_info) && __clang_major__ >= 15
        struct core_reloc_type_based_output *out = (void *)&data.out;
 
        out->struct_exists = bpf_core_type_exists(struct a_struct);
+       out->complex_struct_exists = bpf_core_type_exists(struct a_complex_struct);
        out->union_exists = bpf_core_type_exists(union a_union);
        out->enum_exists = bpf_core_type_exists(enum an_enum);
        out->typedef_named_struct_exists = bpf_core_type_exists(named_struct_typedef);
@@ -92,6 +121,19 @@ int test_core_type_based(void *ctx)
        out->typedef_func_proto_exists = bpf_core_type_exists(func_proto_typedef);
        out->typedef_arr_exists = bpf_core_type_exists(arr_typedef);
 
+       out->struct_matches = bpf_core_type_matches(struct a_struct);
+       out->complex_struct_matches = bpf_core_type_matches(struct a_complex_struct);
+       out->union_matches = bpf_core_type_matches(union a_union);
+       out->enum_matches = bpf_core_type_matches(enum an_enum);
+       out->typedef_named_struct_matches = bpf_core_type_matches(named_struct_typedef);
+       out->typedef_anon_struct_matches = bpf_core_type_matches(anon_struct_typedef);
+       out->typedef_struct_ptr_matches = bpf_core_type_matches(struct_ptr_typedef);
+       out->typedef_int_matches = bpf_core_type_matches(int_typedef);
+       out->typedef_enum_matches = bpf_core_type_matches(enum_typedef);
+       out->typedef_void_ptr_matches = bpf_core_type_matches(void_ptr_typedef);
+       out->typedef_func_proto_matches = bpf_core_type_matches(func_proto_typedef);
+       out->typedef_arr_matches = bpf_core_type_matches(arr_typedef);
+
        out->struct_sz = bpf_core_type_size(struct a_struct);
        out->union_sz = bpf_core_type_size(union a_union);
        out->enum_sz = bpf_core_type_size(enum an_enum);