Commit 516fca5a authored by Andrii Nakryiko's avatar Andrii Nakryiko
Browse files

Merge branch 'libbpf-type-suffixes-and-autocreate-flag-for-struct_ops-maps'

Eduard Zingerman says:

====================
libbpf: type suffixes and autocreate flag for struct_ops maps

Tweak struct_ops related APIs to allow the following features:
- specify version suffixes for stuct_ops map types;
- share same BPF program between several map definitions with
  different local BTF types, assuming only maps with same
  kernel BTF type would be selected for load;
- toggle autocreate flag for struct_ops maps;
- automatically toggle autoload for struct_ops programs referenced
  from struct_ops maps, depending on autocreate status of the
  corresponding map;
- use SEC("?.struct_ops") and SEC("?.struct_ops.link")
  to define struct_ops maps with autocreate == false after object open.

This would allow loading programs like below:

    SEC("struct_ops/foo") int BPF_PROG(foo) { ... }
    SEC("struct_ops/bar") int BPF_PROG(bar) { ... }

    struct bpf_testmod_ops___v1 {
        int (*foo)(void);
    };

    struct bpf_testmod_ops___v2 {
        int (*foo)(void);
        int (*bar)(void);
    };

    /* Assume kernel type name to be 'test_ops' */
    SEC(".struct_ops.link")
    struct test_ops___v1 map_v1 = {
        /* Program 'foo' shared by maps with
         * different local BTF type
         */
        .foo = (void *)foo
    };

    SEC(".struct_ops.link")
    struct test_ops___v2 map_v2 = {
        .foo = (void *)foo,
        .bar = (void *)bar
    };

Assuming the following tweaks are done before loading:

    /* to load v1 */
    bpf_map__set_autocreate(skel->maps.map_v1, true);
    bpf_map__set_autocreate(skel->maps.map_v2, false);

    /* to load v2 */
    bpf_map__set_autocreate(skel->maps.map_v1, false);
    bpf_map__set_autocreate(skel->maps.map_v2, true);

Patch #8 ties autocreate and autoload flags for struct_ops maps and
programs.

Changelog:
- v3 [3] -> v4:
  - changes for multiple styling suggestions from Andrii;
  - patch #5: libbpf log capture now happens for LIBBPF_INFO and
    LIBBPF_WARN messages and does not depend on verbosity flags
    (Andrii);
  - patch #6: fixed runtime crash caused by conflict with newly added
    test case struct_ops_multi_pages;
  - patch #7: fixed free of possibly uninitialized pointer (Daniel)
  - patch #8: simpler algorithm to detect which programs to autoload
    (Andrii);
  - patch #9: added assertions for autoload flag after object load
    (Andrii);
  - patch #12: DATASEC name rewrite in libbpf is now done inplace, no
    new strings added to BTF (Andrii);
  - patch #14: allow any printable characters in DATASEC names when
    kernel validates BTF (Andrii)
- v2 [2] -> v3:
  - moved patch #8 logic to be fully done on load
    (requested by Andrii in offlist discussion);
  - in patch #9 added test case for shadow vars and
    autocreate/autoload interaction.
- v1 [1] -> v2:
  - fixed memory leak in patch #1 (Kui-Feng);
  - improved error messages in patch #2 (Martin, Andrii);
  - in bad_struct_ops selftest from patch #6 added .test_2
    map member setup (David);
  - added utility functions to capture libbpf log from selftests (David)
  - in selftests replaced usage of ...__open_and_load by separate
    calls to ..._open() and ..._load() (Andrii);
  - removed serial_... in selftest definitions (Andrii);
  - improved comments in selftest struct_ops_autocreate
    from patch #7 (David);
  - removed autoload toggling logic incompatible with shadow variables
    from bpf_map__set_autocreate(), instead struct_ops programs
    autoload property is computed at struct_ops maps load phase,
    see patch #8 (Kui-Feng, Martin, Andrii);
  - added support for SEC("?.struct_ops") and SEC("?.struct_ops.link")
    (Andrii).

[1] https://lore.kernel.org/bpf/20240227204556.17524-1-eddyz87@gmail.com/
[2] https://lore.kernel.org/bpf/20240302011920.15302-1-eddyz87@gmail.com/
[3] https://lore.kernel.org/bpf/20240304225156.24765-1-eddyz87@gmail.com/
====================

Link: https://lore.kernel.org/r/20240306104529.6453-1-eddyz87@gmail.com


Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents 0f79bb89 5208930a
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -809,9 +809,23 @@ static bool btf_name_valid_identifier(const struct btf *btf, u32 offset)
	return __btf_name_valid(btf, offset);
}

/* Allow any printable character in DATASEC names */
static bool btf_name_valid_section(const struct btf *btf, u32 offset)
{
	return __btf_name_valid(btf, offset);
	/* offset must be valid */
	const char *src = btf_str_by_offset(btf, offset);
	const char *src_limit;

	/* set a limit on identifier length */
	src_limit = src + KSYM_NAME_LEN;
	src++;
	while (*src && src < src_limit) {
		if (!isprint(*src))
			return false;
		src++;
	}

	return !*src;
}

static const char *__btf_name_by_offset(const struct btf *btf, u32 offset)
+22 −0
Original line number Diff line number Diff line
@@ -147,6 +147,25 @@ static int probe_kern_btf_datasec(int token_fd)
					     strs, sizeof(strs), token_fd));
}

static int probe_kern_btf_qmark_datasec(int token_fd)
{
	static const char strs[] = "\0x\0?.data";
	/* static int a; */
	__u32 types[] = {
		/* int */
		BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4),  /* [1] */
		/* VAR x */                                     /* [2] */
		BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_VAR, 0, 0), 1),
		BTF_VAR_STATIC,
		/* DATASEC ?.data */                            /* [3] */
		BTF_TYPE_ENC(3, BTF_INFO_ENC(BTF_KIND_DATASEC, 0, 1), 4),
		BTF_VAR_SECINFO_ENC(2, 0, 4),
	};

	return probe_fd(libbpf__load_raw_btf((char *)types, sizeof(types),
					     strs, sizeof(strs), token_fd));
}

static int probe_kern_btf_float(int token_fd)
{
	static const char strs[] = "\0float";
@@ -534,6 +553,9 @@ static struct kern_feature_desc {
	[FEAT_ARG_CTX_TAG] = {
		"kernel-side __arg_ctx tag", probe_kern_arg_ctx_tag,
	},
	[FEAT_BTF_QMARK_DATASEC] = {
		"BTF DATASEC names starting from '?'", probe_kern_btf_qmark_datasec,
	},
};

bool feat_supported(struct kern_feature_cache *cache, enum kern_feature_id feat_id)
+151 −58
Original line number Diff line number Diff line
@@ -612,6 +612,7 @@ enum sec_type {
	SEC_BSS,
	SEC_DATA,
	SEC_RODATA,
	SEC_ST_OPS,
};

struct elf_sec_desc {
@@ -627,8 +628,6 @@ struct elf_state {
	Elf *elf;
	Elf64_Ehdr *ehdr;
	Elf_Data *symbols;
	Elf_Data *st_ops_data;
	Elf_Data *st_ops_link_data;
	size_t shstrndx; /* section index for section name strings */
	size_t strtabidx;
	struct elf_sec_desc *secs;
@@ -637,8 +636,7 @@ struct elf_state {
	__u32 btf_maps_sec_btf_id;
	int text_shndx;
	int symbols_shndx;
	int st_ops_shndx;
	int st_ops_link_shndx;
	bool has_st_ops;
};

struct usdt_manager;
@@ -948,7 +946,7 @@ static int find_btf_by_prefix_kind(const struct btf *btf, const char *prefix,
				   const char *name, __u32 kind);

static int
find_struct_ops_kern_types(struct bpf_object *obj, const char *tname,
find_struct_ops_kern_types(struct bpf_object *obj, const char *tname_raw,
			   struct module_btf **mod_btf,
			   const struct btf_type **type, __u32 *type_id,
			   const struct btf_type **vtype, __u32 *vtype_id,
@@ -958,8 +956,12 @@ find_struct_ops_kern_types(struct bpf_object *obj, const char *tname,
	const struct btf_member *kern_data_member;
	struct btf *btf;
	__s32 kern_vtype_id, kern_type_id;
	char tname[256];
	__u32 i;

	snprintf(tname, sizeof(tname), "%.*s",
		 (int)bpf_core_essential_name_len(tname_raw), tname_raw);

	kern_type_id = find_ksym_btf_id(obj, tname, BTF_KIND_STRUCT,
					&btf, mod_btf);
	if (kern_type_id < 0) {
@@ -1027,6 +1029,48 @@ static bool is_valid_st_ops_program(struct bpf_object *obj,
	return false;
}

/* For each struct_ops program P, referenced from some struct_ops map M,
 * enable P.autoload if there are Ms for which M.autocreate is true,
 * disable P.autoload if for all Ms M.autocreate is false.
 * Don't change P.autoload for programs that are not referenced from any maps.
 */
static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
{
	struct bpf_program *prog, *slot_prog;
	struct bpf_map *map;
	int i, j, k, vlen;

	for (i = 0; i < obj->nr_programs; ++i) {
		int should_load = false;
		int use_cnt = 0;

		prog = &obj->programs[i];
		if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
			continue;

		for (j = 0; j < obj->nr_maps; ++j) {
			map = &obj->maps[j];
			if (!bpf_map__is_struct_ops(map))
				continue;

			vlen = btf_vlen(map->st_ops->type);
			for (k = 0; k < vlen; ++k) {
				slot_prog = map->st_ops->progs[k];
				if (prog != slot_prog)
					continue;

				use_cnt++;
				if (map->autocreate)
					should_load = true;
			}
		}
		if (use_cnt)
			prog->autoload = should_load;
	}

	return 0;
}

/* Init the map's fields that depend on kern_btf */
static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
{
@@ -1142,8 +1186,32 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)

			if (mod_btf)
				prog->attach_btf_obj_fd = mod_btf->fd;

			/* if we haven't yet processed this BPF program, record proper
			 * attach_btf_id and member_idx
			 */
			if (!prog->attach_btf_id) {
				prog->attach_btf_id = kern_type_id;
				prog->expected_attach_type = kern_member_idx;
			}

			/* struct_ops BPF prog can be re-used between multiple
			 * .struct_ops & .struct_ops.link as long as it's the
			 * same struct_ops struct definition and the same
			 * function pointer field
			 */
			if (prog->attach_btf_id != kern_type_id) {
				pr_warn("struct_ops init_kern %s func ptr %s: invalid reuse of prog %s in sec %s with type %u: attach_btf_id %u != kern_type_id %u\n",
					map->name, mname, prog->name, prog->sec_name, prog->type,
					prog->attach_btf_id, kern_type_id);
				return -EINVAL;
			}
			if (prog->expected_attach_type != kern_member_idx) {
				pr_warn("struct_ops init_kern %s func ptr %s: invalid reuse of prog %s in sec %s with type %u: expected_attach_type %u != kern_member_idx %u\n",
					map->name, mname, prog->name, prog->sec_name, prog->type,
					prog->expected_attach_type, kern_member_idx);
				return -EINVAL;
			}

			st_ops->kern_func_off[i] = kern_data_off + kern_moff;

@@ -1184,6 +1252,9 @@ static int bpf_object__init_kern_struct_ops_maps(struct bpf_object *obj)
		if (!bpf_map__is_struct_ops(map))
			continue;

		if (!map->autocreate)
			continue;

		err = bpf_map__init_kern_struct_ops(map);
		if (err)
			return err;
@@ -1193,7 +1264,7 @@ static int bpf_object__init_kern_struct_ops_maps(struct bpf_object *obj)
}

static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
				int shndx, Elf_Data *data, __u32 map_flags)
				int shndx, Elf_Data *data)
{
	const struct btf_type *type, *datasec;
	const struct btf_var_secinfo *vsi;
@@ -1251,11 +1322,20 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
			return -ENOMEM;
		map->btf_value_type_id = type_id;

		/* Follow same convention as for programs autoload:
		 * SEC("?.struct_ops") means map is not created by default.
		 */
		if (sec_name[0] == '?') {
			map->autocreate = false;
			/* from now on forget there was ? in section name */
			sec_name++;
		}

		map->def.type = BPF_MAP_TYPE_STRUCT_OPS;
		map->def.key_size = sizeof(int);
		map->def.value_size = type->size;
		map->def.max_entries = 1;
		map->def.map_flags = map_flags;
		map->def.map_flags = strcmp(sec_name, STRUCT_OPS_LINK_SEC) == 0 ? BPF_F_LINK : 0;

		map->st_ops = calloc(1, sizeof(*map->st_ops));
		if (!map->st_ops)
@@ -1290,17 +1370,27 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,

static int bpf_object_init_struct_ops(struct bpf_object *obj)
{
	int err;
	const char *sec_name;
	int sec_idx, err;

	for (sec_idx = 0; sec_idx < obj->efile.sec_cnt; ++sec_idx) {
		struct elf_sec_desc *desc = &obj->efile.secs[sec_idx];

		if (desc->sec_type != SEC_ST_OPS)
			continue;

		sec_name = elf_sec_name(obj, elf_sec_by_idx(obj, sec_idx));
		if (!sec_name)
			return -LIBBPF_ERRNO__FORMAT;

	err = init_struct_ops_maps(obj, STRUCT_OPS_SEC, obj->efile.st_ops_shndx,
				   obj->efile.st_ops_data, 0);
	err = err ?: init_struct_ops_maps(obj, STRUCT_OPS_LINK_SEC,
					  obj->efile.st_ops_link_shndx,
					  obj->efile.st_ops_link_data,
					  BPF_F_LINK);
		err = init_struct_ops_maps(obj, sec_name, sec_idx, desc->data);
		if (err)
			return err;
	}

	return 0;
}

static struct bpf_object *bpf_object__new(const char *path,
					  const void *obj_buf,
					  size_t obj_buf_sz,
@@ -1336,8 +1426,6 @@ static struct bpf_object *bpf_object__new(const char *path,
	obj->efile.obj_buf = obj_buf;
	obj->efile.obj_buf_sz = obj_buf_sz;
	obj->efile.btf_maps_shndx = -1;
	obj->efile.st_ops_shndx = -1;
	obj->efile.st_ops_link_shndx = -1;
	obj->kconfig_map_idx = -1;

	obj->kern_version = get_kernel_version();
@@ -1354,8 +1442,6 @@ static void bpf_object__elf_finish(struct bpf_object *obj)
	elf_end(obj->efile.elf);
	obj->efile.elf = NULL;
	obj->efile.symbols = NULL;
	obj->efile.st_ops_data = NULL;
	obj->efile.st_ops_link_data = NULL;

	zfree(&obj->efile.secs);
	obj->efile.sec_cnt = 0;
@@ -2783,6 +2869,11 @@ static bool section_have_execinstr(struct bpf_object *obj, int idx)
	return sh->sh_flags & SHF_EXECINSTR;
}

static bool starts_with_qmark(const char *s)
{
	return s && s[0] == '?';
}

static bool btf_needs_sanitization(struct bpf_object *obj)
{
	bool has_func_global = kernel_supports(obj, FEAT_BTF_GLOBAL_FUNC);
@@ -2792,9 +2883,10 @@ static bool btf_needs_sanitization(struct bpf_object *obj)
	bool has_decl_tag = kernel_supports(obj, FEAT_BTF_DECL_TAG);
	bool has_type_tag = kernel_supports(obj, FEAT_BTF_TYPE_TAG);
	bool has_enum64 = kernel_supports(obj, FEAT_BTF_ENUM64);
	bool has_qmark_datasec = kernel_supports(obj, FEAT_BTF_QMARK_DATASEC);

	return !has_func || !has_datasec || !has_func_global || !has_float ||
	       !has_decl_tag || !has_type_tag || !has_enum64;
	       !has_decl_tag || !has_type_tag || !has_enum64 || !has_qmark_datasec;
}

static int bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
@@ -2806,6 +2898,7 @@ static int bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
	bool has_decl_tag = kernel_supports(obj, FEAT_BTF_DECL_TAG);
	bool has_type_tag = kernel_supports(obj, FEAT_BTF_TYPE_TAG);
	bool has_enum64 = kernel_supports(obj, FEAT_BTF_ENUM64);
	bool has_qmark_datasec = kernel_supports(obj, FEAT_BTF_QMARK_DATASEC);
	int enum64_placeholder_id = 0;
	struct btf_type *t;
	int i, j, vlen;
@@ -2832,7 +2925,7 @@ static int bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)

			name = (char *)btf__name_by_offset(btf, t->name_off);
			while (*name) {
				if (*name == '.')
				if (*name == '.' || *name == '?')
					*name = '_';
				name++;
			}
@@ -2847,6 +2940,14 @@ static int bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
				vt = (void *)btf__type_by_id(btf, v->type);
				m->name_off = vt->name_off;
			}
		} else if (!has_qmark_datasec && btf_is_datasec(t) &&
			   starts_with_qmark(btf__name_by_offset(btf, t->name_off))) {
			/* replace '?' prefix with '_' for DATASEC names */
			char *name;

			name = (char *)btf__name_by_offset(btf, t->name_off);
			if (name[0] == '?')
				name[0] = '_';
		} else if (!has_func && btf_is_func_proto(t)) {
			/* replace FUNC_PROTO with ENUM */
			vlen = btf_vlen(t);
@@ -2900,14 +3001,13 @@ static int bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
static bool libbpf_needs_btf(const struct bpf_object *obj)
{
	return obj->efile.btf_maps_shndx >= 0 ||
	       obj->efile.st_ops_shndx >= 0 ||
	       obj->efile.st_ops_link_shndx >= 0 ||
	       obj->efile.has_st_ops ||
	       obj->nr_extern > 0;
}

static bool kernel_needs_btf(const struct bpf_object *obj)
{
	return obj->efile.st_ops_shndx >= 0 || obj->efile.st_ops_link_shndx >= 0;
	return obj->efile.has_st_ops;
}

static int bpf_object__init_btf(struct bpf_object *obj,
@@ -3608,12 +3708,14 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
				sec_desc->sec_type = SEC_RODATA;
				sec_desc->shdr = sh;
				sec_desc->data = data;
			} else if (strcmp(name, STRUCT_OPS_SEC) == 0) {
				obj->efile.st_ops_data = data;
				obj->efile.st_ops_shndx = idx;
			} else if (strcmp(name, STRUCT_OPS_LINK_SEC) == 0) {
				obj->efile.st_ops_link_data = data;
				obj->efile.st_ops_link_shndx = idx;
			} else if (strcmp(name, STRUCT_OPS_SEC) == 0 ||
				   strcmp(name, STRUCT_OPS_LINK_SEC) == 0 ||
				   strcmp(name, "?" STRUCT_OPS_SEC) == 0 ||
				   strcmp(name, "?" STRUCT_OPS_LINK_SEC) == 0) {
				sec_desc->sec_type = SEC_ST_OPS;
				sec_desc->shdr = sh;
				sec_desc->data = data;
				obj->efile.has_st_ops = true;
			} else {
				pr_info("elf: skipping unrecognized data section(%d) %s\n",
					idx, name);
@@ -3629,6 +3731,8 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
			if (!section_have_execinstr(obj, targ_sec_idx) &&
			    strcmp(name, ".rel" STRUCT_OPS_SEC) &&
			    strcmp(name, ".rel" STRUCT_OPS_LINK_SEC) &&
			    strcmp(name, ".rel?" STRUCT_OPS_SEC) &&
			    strcmp(name, ".rel?" STRUCT_OPS_LINK_SEC) &&
			    strcmp(name, ".rel" MAPS_ELF_SEC)) {
				pr_info("elf: skipping relo section(%d) %s for section(%d) %s\n",
					idx, name, targ_sec_idx,
@@ -6926,12 +7030,12 @@ static int bpf_object__collect_relos(struct bpf_object *obj)
		data = sec_desc->data;
		idx = shdr->sh_info;

		if (shdr->sh_type != SHT_REL) {
		if (shdr->sh_type != SHT_REL || idx < 0 || idx >= obj->efile.sec_cnt) {
			pr_warn("internal error at %d\n", __LINE__);
			return -LIBBPF_ERRNO__INTERNAL;
		}

		if (idx == obj->efile.st_ops_shndx || idx == obj->efile.st_ops_link_shndx)
		if (obj->efile.secs[idx].sec_type == SEC_ST_OPS)
			err = bpf_object__collect_st_ops_relos(obj, shdr, data);
		else if (idx == obj->efile.btf_maps_shndx)
			err = bpf_object__collect_map_relos(obj, shdr, data);
@@ -8105,11 +8209,20 @@ static void bpf_map_prepare_vdata(const struct bpf_map *map)

static int bpf_object_prepare_struct_ops(struct bpf_object *obj)
{
	struct bpf_map *map;
	int i;

	for (i = 0; i < obj->nr_maps; i++)
		if (bpf_map__is_struct_ops(&obj->maps[i]))
			bpf_map_prepare_vdata(&obj->maps[i]);
	for (i = 0; i < obj->nr_maps; i++) {
		map = &obj->maps[i];

		if (!bpf_map__is_struct_ops(map))
			continue;

		if (!map->autocreate)
			continue;

		bpf_map_prepare_vdata(map);
	}

	return 0;
}
@@ -8135,6 +8248,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch
	err = err ? : bpf_object__resolve_externs(obj, obj->kconfig);
	err = err ? : bpf_object__sanitize_maps(obj);
	err = err ? : bpf_object__init_kern_struct_ops_maps(obj);
	err = err ? : bpf_object_adjust_struct_ops_autoload(obj);
	err = err ? : bpf_object__relocate(obj, obj->btf_custom_path ? : target_btf_path);
	err = err ? : bpf_object__sanitize_and_load_btf(obj);
	err = err ? : bpf_object__create_maps(obj);
@@ -9424,27 +9538,6 @@ static int bpf_object__collect_st_ops_relos(struct bpf_object *obj,
			return -EINVAL;
		}

		/* if we haven't yet processed this BPF program, record proper
		 * attach_btf_id and member_idx
		 */
		if (!prog->attach_btf_id) {
			prog->attach_btf_id = st_ops->type_id;
			prog->expected_attach_type = member_idx;
		}

		/* struct_ops BPF prog can be re-used between multiple
		 * .struct_ops & .struct_ops.link as long as it's the
		 * same struct_ops struct definition and the same
		 * function pointer field
		 */
		if (prog->attach_btf_id != st_ops->type_id ||
		    prog->expected_attach_type != member_idx) {
			pr_warn("struct_ops reloc %s: cannot use prog %s in sec %s with type %u attach_btf_id %u expected_attach_type %u for func ptr %s\n",
				map->name, prog->name, prog->sec_name, prog->type,
				prog->attach_btf_id, prog->expected_attach_type, name);
			return -EINVAL;
		}

		st_ops->progs[member_idx] = prog;

		/* st_ops->data will be exposed to users, being returned by
+2 −0
Original line number Diff line number Diff line
@@ -374,6 +374,8 @@ enum kern_feature_id {
	FEAT_UPROBE_MULTI_LINK,
	/* Kernel supports arg:ctx tag (__arg_ctx) for global subprogs natively */
	FEAT_ARG_CTX_TAG,
	/* Kernel supports '?' at the front of datasec names */
	FEAT_BTF_QMARK_DATASEC,
	__FEAT_CNT,
};

+26 −0
Original line number Diff line number Diff line
@@ -564,6 +564,8 @@ static int bpf_dummy_reg(void *kdata)
{
	struct bpf_testmod_ops *ops = kdata;

	if (ops->test_1)
		ops->test_1();
	/* Some test cases (ex. struct_ops_maybe_null) may not have test_2
	 * initialized, so we need to check for NULL.
	 */
@@ -609,6 +611,29 @@ struct bpf_struct_ops bpf_bpf_testmod_ops = {
	.owner = THIS_MODULE,
};

static int bpf_dummy_reg2(void *kdata)
{
	struct bpf_testmod_ops2 *ops = kdata;

	ops->test_1();
	return 0;
}

static struct bpf_testmod_ops2 __bpf_testmod_ops2 = {
	.test_1 = bpf_testmod_test_1,
};

struct bpf_struct_ops bpf_testmod_ops2 = {
	.verifier_ops = &bpf_testmod_verifier_ops,
	.init = bpf_testmod_ops_init,
	.init_member = bpf_testmod_ops_init_member,
	.reg = bpf_dummy_reg2,
	.unreg = bpf_dummy_unreg,
	.cfi_stubs = &__bpf_testmod_ops2,
	.name = "bpf_testmod_ops2",
	.owner = THIS_MODULE,
};

extern int bpf_fentry_test1(int a);

static int bpf_testmod_init(void)
@@ -620,6 +645,7 @@ static int bpf_testmod_init(void)
	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_testmod_kfunc_set);
	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &bpf_testmod_kfunc_set);
	ret = ret ?: register_bpf_struct_ops(&bpf_bpf_testmod_ops, bpf_testmod_ops);
	ret = ret ?: register_bpf_struct_ops(&bpf_testmod_ops2, bpf_testmod_ops2);
	if (ret < 0)
		return ret;
	if (bpf_fentry_test1(0) < 0)
Loading