All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons
@ 2024-02-14  2:08 thinker.li
  2024-02-14  2:08 ` [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary thinker.li
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: thinker.li @ 2024-02-14  2:08 UTC (permalink / raw)
  To: bpf, ast, martin.lau, song, kernel-team, andrii
  Cc: sinquersw, kuifeng, Kui-Feng Lee

From: Kui-Feng Lee <thinker.li@gmail.com>

This RFC is for gathering feedback/opinions on the design.
Based on the feedback received for v1, I made some modifications.

== Pointers to Shadow Copies ==

With the current implementation, the code generator will create a
pointer to a shadow copy of the struct_ops map for each map. For
instance, if we define a testmod_1 as a struct_ops map, we can access
its corresponding shadow variable "data" using the pointer.

    skel->struct_ops.testmod1->data

== Shadow Info ==

The code generator also generates a shadow info to describe the layout
of the data pointed to by all these pointers. For instance, the
following shadow info describes the layout of a struct_ops map called
testmod_1, which has 3 members: test_1, test_2, and data.

    static struct bpf_struct_ops_member_info member_info_testmod_1[] = {
    	{
    		.name = "test_1",
    		.offset = .....,
    		.size = .....,
    	},
    	{
    		.name = "test_2",
    		.offset = .....,
    		.size = .....,
    	},
    	{
    		.name = "data",
    		.offset = .....,
    		.size = .....,
    	},
    };
    static struct bpf_struct_ops_map_info map_info[] = {
    	{
    		.name = "testmod_1",
    		.members = member_info_testmod_1,
    		.cnt = ARRAY_SIZE(member_info_testmod_1),
    		.data_size = sizeof(struct_ops->testmod_1),
    	},
    };
    static struct bpf_struct_ops_shadow_info shadow_info = {
    	.maps = map_info,
    	.cnt = ARRAY_SIZE(map_info),
    };

A shadow info describes the layout of the shadow copies of all
struct_ops maps included in a skeleton. (Defined in *__shadow_info())

== libbpf Creates Shadow Copies ==

This shadow info should be passed to bpf_object__open_skeleton() as a
part of "opts" so that libbpf can create shadow copies with the layout
described by the shadow info. For now, *__open() in the skeleton will
automatically pass the shadow info to bpf_object__open_skeleton(),
looking like the following example.

    static inline struct struct_ops_module *
    struct_ops_module__open(void)
    {
    	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
    
    	opts.struct_ops_shadow = struct_ops_module__shadow_info();
    
    	return struct_ops_module__open_opts(*** BLURB HERE ***opts);
    }

The function bpf_map__initial_value() will return the shadow copy that
is created based on the received shadow info. Therefore, in the
function *__open_opts() in the skeleton, the pointers to shadow copies
will be initialized with the values returned from
bpf_map__initial_value(). For instance,

   obj->struct_ops.testmod_1 =
	bpf_map__initial_value(obj->maps.testmod_1, NULL);

This line of code will be included in the *__open_opts() function. If
the opts.struct_ops_shadow is not set, bpf_map__initial_value() will
return a NULL.

========================================
DESCRIPTION form v1
========================================

Create shadow variables for the fields of struct_ops maps in a skeleton
with the same name as the respective fields. For instance, if struct
bpf_testmod_ops has a "data" field as following, you can access or modify
its value through a shadow variable also named "data" in the skeleton.

  SEC(".struct_ops.link")
  struct bpf_testmod_ops testmod_1 = {
      .data = 0x1,
  };

Then, you can change the value in the following manner as shown in the code
below.

  skel->struct_ops.testmod_1->data = 13;

It is helpful to configure a struct_ops map during the execution of a
program. For instance, you can include a flag in a struct_ops type to
modify the way the kernel handles an instance. By implementing this
feature, a user space program can alter the flag's value prior to loading
an instance into the kernel.

The code generator for skeletons will produce code that copies values to
shadow variables from the internal data buffer when a skeleton is
opened. It will also copy values from shadow variables back to the internal
data buffer before a skeleton is loaded into the kernel.

The code generator will calculate the offset of a field and generate code
that copies the value of the field from or to the shadow variable to or
from the struct_ops internal data, with an offset relative to the beginning
of the internal data. For instance, if the "data" field in struct
bpf_testmod_ops is situated 16 bytes from the beginning of the struct, the
address for the "data" field of struct bpf_testmod_ops will be the starting
address plus 16.

The offset is calculated during code generation, so it is always in line
with the internal data in the skeleton. Even if the user space programs and
the BPF program were not compiled together, any differences in versions
would not impact correctness. This means that even if the BPF program and
the user space program reference different versions of the "struct
bpf_testmod_ops" and have different offsets for "data", these offsets
computed by the code generator would still function correctly.

The user space programs can only modify values by using these shadow
variables when the skeleton is open, but before being loaded. Once the
skeleton is loaded, the value is copied to the kernel, and any future
changes only affect the shadow variables in the user space memory and do
not update the kernel space. The shadow variables are not initialized
before opening a skeleton, so you cannot update values through them before
opening.

For function pointers (operators), you can change/replace their values with
other BPF programs. For example, the test case in test_struct_ops_module.c
points .test_2 to test_3() while it was pointed to test_2() by assigning a
new value to the shadow variable.

  skel->st_ops_vars.testmod_1->test_2 = skel->progs.test_3;

However, you need to turn off autoload for test_2() since it is not
assigned to any struct_ops map anymore. Or, it will fails to load test_2().

 bpf_program__set_autoload(skel->progs.test_2, false);

You can define more struct_ops programs than necessary. However, you need
to turn autoload off for unused ones.

---

v1: https://lore.kernel.org/all/20240124224130.859921-1-thinker.li@gmail.com/

Kui-Feng Lee (3):
  libbpf: Create a shadow copy for each struct_ops map if necessary.
  bpftool: generated shadow variables for struct_ops maps.
  selftests/bpf: Test if shadow variables work.

 tools/bpf/bpftool/gen.c                       | 358 +++++++++++++++++-
 tools/lib/bpf/libbpf.c                        | 195 +++++++++-
 tools/lib/bpf/libbpf.h                        |  34 +-
 tools/lib/bpf/libbpf.map                      |   1 +
 tools/lib/bpf/libbpf_internal.h               |   1 +
 .../selftests/bpf/bpf_testmod/bpf_testmod.c   |   6 +-
 .../selftests/bpf/bpf_testmod/bpf_testmod.h   |   1 +
 .../bpf/prog_tests/test_struct_ops_module.c   |  16 +-
 .../selftests/bpf/progs/struct_ops_module.c   |   8 +
 9 files changed, 596 insertions(+), 24 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary.
  2024-02-14  2:08 [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons thinker.li
@ 2024-02-14  2:08 ` thinker.li
  2024-02-15 23:55   ` Andrii Nakryiko
  2024-02-14  2:08 ` [RFC bpf-next v2 2/3] bpftool: generated shadow variables for struct_ops maps thinker.li
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 11+ messages in thread
From: thinker.li @ 2024-02-14  2:08 UTC (permalink / raw)
  To: bpf, ast, martin.lau, song, kernel-team, andrii
  Cc: sinquersw, kuifeng, Kui-Feng Lee

From: Kui-Feng Lee <thinker.li@gmail.com>

If the user has passed a shadow info for a struct_ops map along with struct
bpf_object_open_opts, a shadow copy will be created for the map and
returned from bpf_map__initial_value().

The user can read and write shadow variables through the shadow copy, which
is placed in the struct pointed by skel->struct_ops.FOO, where FOO is the
map name.

The value of a shadow variable will be used to update the value of the map
when loading the map to the kernel.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 tools/lib/bpf/libbpf.c          | 195 ++++++++++++++++++++++++++++++--
 tools/lib/bpf/libbpf.h          |  34 +++++-
 tools/lib/bpf/libbpf.map        |   1 +
 tools/lib/bpf/libbpf_internal.h |   1 +
 4 files changed, 220 insertions(+), 11 deletions(-)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 01f407591a92..ce9c4cdb2dc5 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -487,6 +487,14 @@ struct bpf_struct_ops {
 	 * from "data".
 	 */
 	void *kern_vdata;
+	/* Description of the layout that a shadow copy should look like.
+	 */
+	const struct bpf_struct_ops_map_info *shadow_info;
+	/* A shadow copy of the struct_ops data created according to the
+	 * layout described by shadow_info.
+	 */
+	void *shadow_data;
+	__u32 shadow_data_size;
 	__u32 type_id;
 };
 
@@ -1027,7 +1035,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
 	struct module_btf *mod_btf;
 	void *data, *kern_data;
 	const char *tname;
-	int err;
+	int err, j;
 
 	st_ops = map->st_ops;
 	type = st_ops->type;
@@ -1083,9 +1091,18 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
 		}
 
 		moff = member->offset / 8;
-		kern_moff = kern_member->offset / 8;
-
 		mdata = data + moff;
+		if (st_ops->shadow_data) {
+			for (j = 0; j < st_ops->shadow_info->cnt; j++) {
+				if (strcmp(mname, st_ops->shadow_info->members[j].name))
+					continue;
+				moff = st_ops->shadow_info->members[j].offset;
+				mdata = st_ops->shadow_data + moff;
+				break;
+			}
+		}
+
+		kern_moff = kern_member->offset / 8;
 		kern_mdata = kern_data + kern_moff;
 
 		mtype = skip_mods_and_typedefs(btf, member->type, &mtype_id);
@@ -1102,6 +1119,9 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
 		if (btf_is_ptr(mtype)) {
 			struct bpf_program *prog;
 
+			if (st_ops->shadow_data)
+				st_ops->progs[i] =
+					*(struct bpf_program **)mdata;
 			prog = st_ops->progs[i];
 			if (!prog)
 				continue;
@@ -1172,8 +1192,108 @@ static int bpf_object__init_kern_struct_ops_maps(struct bpf_object *obj)
 	return 0;
 }
 
+static int init_struct_ops_shadow(struct bpf_map *map,
+				  const struct btf_type *t)
+{
+	struct btf *btf = map->obj->btf;
+	struct bpf_struct_ops *st_ops = map->st_ops;
+	const struct btf_member *m;
+	const struct btf_type *mt;
+	const struct bpf_struct_ops_member_info *info;
+	const char *name;
+	char *data;
+	int i, j, err;
+
+	data = calloc(1, st_ops->shadow_info->data_size);
+	if (!data)
+		return -ENOMEM;
+
+	for (i = 0, m = btf_members(t); i < btf_vlen(t); i++, m++) {
+		name = btf__name_by_offset(btf, m->name_off);
+		if (!name) {
+			pr_warn("struct_ops init_shadow %s: member %d has no name\n",
+				map->name, i);
+			err = -EINVAL;
+			goto err_out;
+		}
+		for (j = 0, info = st_ops->shadow_info->members;
+		     j < st_ops->shadow_info->cnt;
+		     j++, info++) {
+			if (strcmp(name, info->name) == 0)
+				break;
+		}
+		if (j == st_ops->shadow_info->cnt)
+			info = NULL;
+		mt = skip_mods_and_typedefs(btf, m->type, NULL);
+
+		switch (btf_kind(mt)) {
+		case BTF_KIND_INT:
+		case BTF_KIND_FLOAT:
+		case BTF_KIND_ENUM:
+		case BTF_KIND_ENUM64:
+			if (!info) {
+				pr_warn("struct_ops init_shadow %s: member %s not found in map info\n",
+					map->name, name);
+				err = -EINVAL;
+				goto err_out;
+			}
+			if (info->size != mt->size) {
+				pr_warn("struct_ops init_shadow %s: member %s size mismatch: %u != %u\n",
+					map->name, name, info->size, mt->size);
+				err = -EINVAL;
+				goto err_out;
+			}
+			memcpy(data + info->offset, st_ops->data + m->offset / 8, mt->size);
+			break;
+
+		case BTF_KIND_PTR:
+			if (!resolve_func_ptr(btf, m->type, NULL)) {
+				if (!info)
+					break;
+				pr_warn("struct_ops init_shadow %s: member %s is not a func ptr\n",
+					map->name, name);
+				err = -ENOTSUP;
+				goto err_out;
+			}
+			if (!info) {
+				pr_warn("struct_ops init_shadow %s: member %s not found in map info\n",
+					map->name, name);
+				err = -EINVAL;
+				goto err_out;
+			}
+			if (info->size != sizeof(void *)) {
+				pr_warn("struct_ops init_shadow %s: member %s size mismatch: %u != %lu\n",
+					map->name, name, info->size, sizeof(void *));
+				err = -EINVAL;
+				goto err_out;
+			}
+			*((struct bpf_program **)(data + info->offset)) =
+				st_ops->progs[i];
+			break;
+
+		default:
+			if (info) {
+				pr_warn("struct_ops init_shadow %s: member %s not supported type\n",
+					map->name, name);
+				err = -ENOTSUP;
+				goto err_out;
+			}
+			break;
+		}
+	}
+
+	st_ops->shadow_data = data;
+
+	return 0;
+
+err_out:
+	free(data);
+	return err;
+}
+
 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, __u32 map_flags,
+				const struct bpf_struct_ops_shadow_info *shadow)
 {
 	const struct btf_type *type, *datasec;
 	const struct btf_var_secinfo *vsi;
@@ -1182,7 +1302,7 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
 	__s32 type_id, datasec_id;
 	const struct btf *btf;
 	struct bpf_map *map;
-	__u32 i;
+	__u32 i, j;
 
 	if (shndx == -1)
 		return 0;
@@ -1260,6 +1380,16 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
 		st_ops->type = type;
 		st_ops->type_id = type_id;
 
+		if (shadow) {
+			for (j = 0; j < shadow->cnt; j++) {
+				if (strcmp(shadow->maps[j].name, var_name))
+					continue;
+				st_ops->shadow_info = &shadow->maps[j];
+				break;
+			}
+
+		}
+
 		pr_debug("struct_ops init: struct %s(type_id=%u) %s found at offset %u\n",
 			 tname, type_id, var_name, vsi->offset);
 	}
@@ -1267,16 +1397,19 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
 	return 0;
 }
 
-static int bpf_object_init_struct_ops(struct bpf_object *obj)
+static int bpf_object_init_struct_ops(struct bpf_object *obj,
+				      const struct bpf_struct_ops_shadow_info *shadow)
 {
 	int err;
 
 	err = init_struct_ops_maps(obj, STRUCT_OPS_SEC, obj->efile.st_ops_shndx,
-				   obj->efile.st_ops_data, 0);
+				   obj->efile.st_ops_data, 0, shadow);
 	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);
+					  BPF_F_LINK,
+					  shadow);
+
 	return err;
 }
 
@@ -2145,7 +2278,7 @@ skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id)
 	return t;
 }
 
-static const struct btf_type *
+const struct btf_type *
 resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id)
 {
 	const struct btf_type *t;
@@ -2736,17 +2869,19 @@ static int bpf_object__init_user_btf_maps(struct bpf_object *obj, bool strict,
 static int bpf_object__init_maps(struct bpf_object *obj,
 				 const struct bpf_object_open_opts *opts)
 {
+	const struct bpf_struct_ops_shadow_info *shadow_info;
 	const char *pin_root_path;
 	bool strict;
 	int err = 0;
 
 	strict = !OPTS_GET(opts, relaxed_maps, false);
 	pin_root_path = OPTS_GET(opts, pin_root_path, NULL);
+	shadow_info = OPTS_GET(opts, struct_ops_shadow, NULL);
 
 	err = bpf_object__init_user_btf_maps(obj, strict, pin_root_path);
 	err = err ?: bpf_object__init_global_data_maps(obj);
 	err = err ?: bpf_object__init_kconfig_map(obj);
-	err = err ?: bpf_object_init_struct_ops(obj);
+	err = err ?: bpf_object_init_struct_ops(obj, shadow_info);
 
 	return err;
 }
@@ -7528,6 +7663,33 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object
 	return 0;
 }
 
+/* Create a shadow copy for each struct_ops map if it has shadow info.
+ *
+ * The shadow copy should be created after bpf_object__collect_relos()
+ * since st_ops->progs is initialized in that function.
+ */
+static int bpf_object__init_shadow(struct bpf_object *obj)
+{
+	struct bpf_map *map;
+	int err;
+
+	bpf_object__for_each_map(map, obj) {
+		if (!bpf_map__is_struct_ops(map))
+			continue;
+
+		if (!map->st_ops->shadow_info)
+			continue;
+		err = init_struct_ops_shadow(map, map->st_ops->type);
+		if (err) {
+			pr_warn("map '%s': failed to init shadow: %d\n",
+				map->name, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
 static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf, size_t obj_buf_sz,
 					  const struct bpf_object_open_opts *opts)
 {
@@ -7624,6 +7786,7 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 	err = err ? : bpf_object__init_maps(obj, opts);
 	err = err ? : bpf_object_init_progs(obj, opts);
 	err = err ? : bpf_object__collect_relos(obj);
+	err = err ? : bpf_object__init_shadow(obj);
 	if (err)
 		goto out;
 
@@ -8588,6 +8751,7 @@ static void bpf_map__destroy(struct bpf_map *map)
 	}
 
 	if (map->st_ops) {
+		zfree(&map->st_ops->shadow_data);
 		zfree(&map->st_ops->data);
 		zfree(&map->st_ops->progs);
 		zfree(&map->st_ops->kern_func_off);
@@ -9877,6 +10041,12 @@ int bpf_map__set_initial_value(struct bpf_map *map,
 
 void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
 {
+	if (bpf_map__is_struct_ops(map)) {
+		if (psize)
+			*psize = map->st_ops->shadow_data_size;
+		return map->st_ops->shadow_data;
+	}
+
 	if (!map->mmaped)
 		return NULL;
 	*psize = map->def.value_size;
@@ -13462,3 +13632,8 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
 	free(s->progs);
 	free(s);
 }
+
+__u32 bpf_map__struct_ops_type(const struct bpf_map *map)
+{
+	return map->st_ops->type_id;
+}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 5723cbbfcc41..b435cafefe7a 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -109,6 +109,27 @@ LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn);
 /* Hide internal to user */
 struct bpf_object;
 
+/* Description of a member in the struct_ops type for a map. */
+struct bpf_struct_ops_member_info {
+	const char *name;
+	__u32 offset;
+	__u32 size;
+};
+
+/* Description of the layout of a shadow copy for a struct_ops map. */
+struct bpf_struct_ops_map_info {
+	/* The name of the struct_ops map */
+	const char *name;
+	const struct bpf_struct_ops_member_info *members;
+	__u32 cnt;
+	__u32 data_size;
+};
+
+struct bpf_struct_ops_shadow_info {
+	const struct bpf_struct_ops_map_info *maps;
+	__u32 cnt;
+};
+
 struct bpf_object_open_opts {
 	/* size of this struct, for forward/backward compatibility */
 	size_t sz;
@@ -197,9 +218,18 @@ struct bpf_object_open_opts {
 	 */
 	const char *bpf_token_path;
 
+	/* A list of shadow info for every struct_ops map.  A shadow info
+	 * provides the information used by libbpf to map the offsets of
+	 * struct members of a struct_ops type from BTF to the offsets of
+	 * the corresponding members in the shadow copy in the user
+	 * space. It ensures that the shadow copy provided by the libbpf
+	 * can be accessed by the user space program correctly.
+	 */
+	const struct bpf_struct_ops_shadow_info *struct_ops_shadow;
+
 	size_t :0;
 };
-#define bpf_object_open_opts__last_field bpf_token_path
+#define bpf_object_open_opts__last_field struct_ops_shadow
 
 /**
  * @brief **bpf_object__open()** creates a bpf_object by opening
@@ -839,6 +869,8 @@ struct bpf_map;
 LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
 LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
 
+LIBBPF_API __u32 bpf_map__struct_ops_type(const struct bpf_map *map);
+
 struct bpf_iter_attach_opts {
 	size_t sz; /* size of this struct for forward/backward compatibility */
 	union bpf_iter_link_info *link_info;
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 86804fd90dd1..e0efc85114df 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -413,4 +413,5 @@ LIBBPF_1.4.0 {
 		bpf_token_create;
 		btf__new_split;
 		btf_ext__raw_data;
+		bpf_map__struct_ops_type;
 } LIBBPF_1.3.0;
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index ad936ac5e639..aec6d57fe5d1 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -234,6 +234,7 @@ struct btf_type;
 struct btf_type *btf_type_by_id(const struct btf *btf, __u32 type_id);
 const char *btf_kind_str(const struct btf_type *t);
 const struct btf_type *skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
+const struct btf_type *resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id);
 
 static inline enum btf_func_linkage btf_func_linkage(const struct btf_type *t)
 {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC bpf-next v2 2/3] bpftool: generated shadow variables for struct_ops maps.
  2024-02-14  2:08 [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons thinker.li
  2024-02-14  2:08 ` [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary thinker.li
@ 2024-02-14  2:08 ` thinker.li
  2024-02-14  2:08 ` [RFC bpf-next v2 3/3] selftests/bpf: Test if shadow variables work thinker.li
  2024-02-15 23:50 ` [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons Andrii Nakryiko
  3 siblings, 0 replies; 11+ messages in thread
From: thinker.li @ 2024-02-14  2:08 UTC (permalink / raw)
  To: bpf, ast, martin.lau, song, kernel-team, andrii
  Cc: sinquersw, kuifeng, Kui-Feng Lee

From: Kui-Feng Lee <thinker.li@gmail.com>

Declares and defines a pointer to a shadow copy for each struct_ops map.

Create shadow info to describe the layout of shadow copies of every
struct_ops map. The shadow info will be passed to libbpf when calling
*__open() of the skeleton.  And, initialize the pointers to shadow copies
created by the libbpf.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 tools/bpf/bpftool/gen.c | 358 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 350 insertions(+), 8 deletions(-)

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index a9334c57e859..fb5d01dcfe17 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -909,6 +909,281 @@ codegen_progs_skeleton(struct bpf_object *obj, size_t prog_cnt, bool populate_li
 	}
 }
 
+/* A callback (cb) will be called for each member that can have a shadow of
+ * a map.
+ *
+ * For now, it supports only scalar types and function pointers.
+ */
+static int walk_st_ops_shadow_vars(struct btf *btf,
+				   const char *ident,
+				   const struct bpf_map *map,
+				   int (*cb)(const struct btf *btf,
+					     const char *ident,
+					     const struct btf_member *m,
+					     __u32 member_type_id,
+					     bool func_ptr))
+{
+	const struct btf_type *map_type, *member_type;
+	const struct btf_member *m;
+	__u32 map_type_id, member_type_id;
+	int i, err;
+
+	map_type_id = bpf_map__struct_ops_type(map);
+	if (map_type_id == 0)
+		return -EINVAL;
+	map_type = btf__type_by_id(btf, map_type_id);
+	if (!map_type)
+		return -EINVAL;
+
+	for (i = 0, m = btf_members(map_type);
+	     i < btf_vlen(map_type);
+	     i++, m++) {
+		member_type = skip_mods_and_typedefs(btf, m->type,
+						     &member_type_id);
+		if (!member_type)
+			return -EINVAL;
+
+		switch (btf_kind(member_type)) {
+		case BTF_KIND_INT:
+		case BTF_KIND_FLOAT:
+		case BTF_KIND_ENUM:
+		case BTF_KIND_ENUM64:
+			/* scalar type */
+			err = cb(btf, ident, m, member_type_id, false);
+			if (err)
+				return err;
+			break;
+
+		case BTF_KIND_PTR:
+			if (resolve_func_ptr(btf, m->type, NULL)) {
+				/* Function pointer */
+				err = cb(btf, ident, m, member_type_id, true);
+				if (err)
+					return err;
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int gen_st_ops_sahdow_var(const struct btf *btf,
+				 const char *ident,
+				 const struct btf_member *m,
+				 __u32 member_type_id,
+				 bool func_ptr)
+{
+	DECLARE_LIBBPF_OPTS(btf_dump_emit_type_decl_opts, opts,
+			    .indent_level = 3,
+			    .strip_mods = false,
+			    );
+	struct btf_dump *d = NULL;
+	int err = 0;
+
+	if (func_ptr) {
+		printf("\t\t\tconst struct bpf_program *%s;\n",
+		       btf__name_by_offset(btf, m->name_off));
+	} else {
+		d = btf_dump__new(btf, codegen_btf_dump_printf, NULL, NULL);
+		if (!d) {
+			err = -errno;
+			goto out;
+		}
+
+		printf("\t\t\t");
+		opts.field_name = btf__name_by_offset(btf, m->name_off);
+		err = btf_dump__emit_type_decl(d, member_type_id, &opts);
+		if (err)
+			goto out;
+		printf(";\n");
+	}
+
+out:
+	btf_dump__free(d);
+
+	return err;
+}
+
+/* Add a pointer of a shadow copy to the skeleton for a struct_ops map.
+ *
+ * A shadow copy of a struct_ops map is used to store the sahdow variables.
+ * Shadow variables are a mirror of member fields in a struct_ops map
+ * defined in the BPF program. They serve as a way to access and change the
+ * values in the map. For example, in struct bpf_testmod_ops, it defines
+ * three fields: test_1, test_2, and data. And, it defines an instance as
+ * testmod_1 in the program. Then, the skeleton will have three shadow
+ * variables: test_1, test_2, and data in skel->st_ops.testmod_1.
+ *
+ * Now, it doesn't support pointer type fields except function pointers.
+ * For non-function pointer fields, the shadow variables will be in the
+ * same type as the original fields, but all modifiers (const,
+ * volatile,...) are removed.
+ *
+ * For function pointer fields, the shadow variables will be in the "struct
+ * bpf_program *" type.
+ */
+static int gen_st_ops_shadow_copy(struct btf *btf, const char *ident,
+				  const struct bpf_map *map)
+{
+	int err;
+
+	printf("\t\tstruct {\n");
+
+	err = walk_st_ops_shadow_vars(btf, ident, map, gen_st_ops_sahdow_var);
+	if (err)
+		return err;
+
+	printf("\t\t} *%s;\n", ident);
+
+	return 0;
+}
+
+static int gen_st_ops_member_info(const struct btf *btf,
+				  const char *ident,
+				  const struct btf_member *m,
+				  __u32 member_type_id,
+				  bool func_ptr)
+{
+	const char *member_name = btf__name_by_offset(btf, m->name_off);
+
+	codegen("\
+	\n\
+			{						    \n\
+				.name = \"%2$s\",			    \n\
+				.offset = (__u32)(uintptr_t)&((typeof(struct_ops->%1$s))0)->%2$s,\n\
+				.size = sizeof(((typeof(struct_ops->%1$s))0)->%2$s),\n\
+			},						    \n\
+	", ident, member_name);
+
+	return 0;
+}
+
+static int gen_st_ops_member_infos(struct btf *btf, const char *ident,
+				   const struct bpf_map *map)
+{
+	int err;
+
+	printf("\tstatic struct bpf_struct_ops_member_info member_info_%s[] = {\n",
+	       ident);
+
+	err = walk_st_ops_shadow_vars(btf, ident, map,
+				      gen_st_ops_member_info);
+	if (err)
+		return err;
+
+	printf("\t};\n");
+
+	return 0;
+}
+
+static void gen_st_ops_map_info(const char *ident)
+{
+	codegen("\
+		\n\
+			{						    \n\
+				.name = \"%1$s\",			    \n\
+				.members = member_info_%1$s,		    \n\
+				.cnt = ARRAY_SIZE(member_info_%1$s),	    \n\
+				.data_size = sizeof(*struct_ops->%1$s),	    \n\
+			},						    \n\
+		", ident);
+}
+
+/* Generate information of shadow variables for every struct_ops map.
+ *
+ * The shadow information of map includes the name, the offset, and the
+ * size of each supported member field in the struct_ops map to describe
+ * the layout of a shadow copy. The shadow info of struct_ops maps will be
+ * passed to libbpf to create a shadow copy for each struct_ops map.
+ *
+ * For example, in struct_ops_module.c, it defines "testmod_1" in struct
+ * bpf_testmod_ops, which has three fields: test_1, test_2, and data. Then,
+ * the shadow information will be:
+ *
+ * static struct bpf_struct_ops_member_info member_info_testmod_1[] = {
+ *	{
+ *		.name = "test_1",
+ *		.offset = .....,
+ *		.size = .....,
+ *	},
+ *	{
+ *		.name = "test_2",
+ *		.offset = .....,
+ *		.size = .....,
+ *	},
+ *	{
+ *		.name = "data",
+ *		.offset = .....,
+ *		.size = .....,
+ *	},
+ * };
+ * static struct bpf_struct_ops_map_info map_info[] = {
+ *	{
+ *		.name = "testmod_1",
+ *		.members = member_info_testmod_1,
+ *		.cnt = ARRAY_SIZE(member_info_testmod_1),
+ *		.data_size = sizeof(struct_ops->testmod_1),
+ *	},
+ * };
+ * static struct bpf_struct_ops_shadow_info shadow_info = {
+ *	.maps = map_info,
+ *	.cnt = ARRAY_SIZE(map_info),
+ * };
+ *
+ * testmod_1_shadow_info() will return the pointer of "sahdow_info" defined
+ * above.
+ */
+static int gen_st_ops_shadow_info(struct bpf_object *obj, const char *obj_name)
+{
+	struct btf *btf = bpf_object__btf(obj);
+	struct bpf_map *map;
+
+	codegen("\
+		\n\
+		static inline struct bpf_struct_ops_shadow_info *	    \n\
+		%1$s__shadow_info()					    \n\
+		{							    \n\
+			typeof(((struct %1$s *)0)->struct_ops) *struct_ops = NULL;\n\
+		", obj_name);
+	bpf_object__for_each_map(map, obj) {
+		const char *ident = bpf_map__name(map);
+		int err;
+
+		if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+			continue;
+
+		err = gen_st_ops_member_infos(btf, ident, map);
+		if (err)
+			return err;
+	}
+	printf("\tstatic struct bpf_struct_ops_map_info map_info[] = {\n");
+	bpf_object__for_each_map(map, obj) {
+		const char *ident = bpf_map__name(map);
+
+		if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+			continue;
+
+		gen_st_ops_map_info(ident);
+	}
+	codegen("\
+		\n\
+			};						    \n\
+			static struct bpf_struct_ops_shadow_info shadow_info = {\n\
+				.maps = map_info,			    \n\
+				.cnt = ARRAY_SIZE(map_info),		    \n\
+			};						    \n\
+		\n\
+			return &shadow_info;				    \n\
+		}							    \n\
+		\n");
+
+	return 0;
+}
+
 static int do_skeleton(int argc, char **argv)
 {
 	char header_guard[MAX_OBJ_NAME_LEN + sizeof("__SKEL_H__")];
@@ -923,6 +1198,7 @@ static int do_skeleton(int argc, char **argv)
 	struct bpf_map *map;
 	struct btf *btf;
 	struct stat st;
+	int st_ops_cnt = 0;
 
 	if (!REQ_ARGS(1)) {
 		usage();
@@ -1048,8 +1324,33 @@ static int do_skeleton(int argc, char **argv)
 				printf("\t\tstruct bpf_map_desc %s;\n", ident);
 			else
 				printf("\t\tstruct bpf_map *%s;\n", ident);
+			if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS)
+				st_ops_cnt++;
 		}
 		printf("\t} maps;\n");
+		if (st_ops_cnt) {
+			/* Generate the pointers to shadow copies of
+			 * struct_ops maps.
+			 */
+			btf = bpf_object__btf(obj);
+			if (!btf) {
+				err = -1;
+				p_err("need btf type information for %s", obj_name);
+				goto out;
+			}
+
+			printf("\tstruct {\n");
+			bpf_object__for_each_map(map, obj) {
+				if (!get_map_ident(map, ident, sizeof(ident)))
+					continue;
+				if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+					continue;
+				err = gen_st_ops_shadow_copy(btf, ident, map);
+				if (err)
+					goto out;
+			}
+			printf("\t} struct_ops;\n");
+		}
 	}
 
 	if (prog_cnt) {
@@ -1075,12 +1376,9 @@ static int do_skeleton(int argc, char **argv)
 		printf("\t} links;\n");
 	}
 
-	btf = bpf_object__btf(obj);
-	if (btf) {
-		err = codegen_datasecs(obj, obj_name);
-		if (err)
-			goto out;
-	}
+	err = codegen_datasecs(obj, obj_name);
+	if (err)
+		goto out;
 	if (use_loader) {
 		err = gen_trace(obj, obj_name, header_guard);
 		goto out;
@@ -1099,7 +1397,17 @@ static int do_skeleton(int argc, char **argv)
 			static inline const void *elf_bytes(size_t *sz);    \n\
 		#endif /* __cplusplus */				    \n\
 		};							    \n\
-									    \n\
+		\n\
+		", obj_name);
+
+	if (st_ops_cnt) {
+		err = gen_st_ops_shadow_info(obj, obj_name);
+		if (err)
+			goto out;
+	}
+
+	codegen("\
+		\n\
 		static void						    \n\
 		%1$s__destroy(struct %1$s *obj)				    \n\
 		{							    \n\
@@ -1133,6 +1441,24 @@ static int do_skeleton(int argc, char **argv)
 			if (err)					    \n\
 				goto err_out;				    \n\
 									    \n\
+		", obj_name);
+	if (st_ops_cnt) {
+		/* Initialize the pointers of struct_ops shadow copies */
+		bpf_object__for_each_map(map, obj) {
+			if (!get_map_ident(map, ident, sizeof(ident)))
+				continue;
+			if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+				continue;
+			codegen("\
+				\n\
+					obj->struct_ops.%1$s =		    \n\
+						bpf_map__initial_value(obj->maps.%1$s, NULL);\n\
+				\n\
+				", ident);
+		}
+	}
+	codegen("\
+		\n\
 			return obj;					    \n\
 		err_out:						    \n\
 			%1$s__destroy(obj);				    \n\
@@ -1143,7 +1469,23 @@ static int do_skeleton(int argc, char **argv)
 		static inline struct %1$s *				    \n\
 		%1$s__open(void)					    \n\
 		{							    \n\
-			return %1$s__open_opts(NULL);			    \n\
+		", obj_name);
+	if (st_ops_cnt)
+		codegen("\
+			\n\
+				DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);\n\
+			\n\
+				opts.struct_ops_shadow = %1$s__shadow_info();\n\
+			\n\
+				return %1$s__open_opts(&opts);	     \n\
+			", obj_name);
+	else
+		codegen("\
+			\n\
+				return %1$s__open_opts(NULL);		     \n\
+			", obj_name);
+	codegen("\
+		\n\
 		}							    \n\
 									    \n\
 		static inline int					    \n\
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC bpf-next v2 3/3] selftests/bpf: Test if shadow variables work.
  2024-02-14  2:08 [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons thinker.li
  2024-02-14  2:08 ` [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary thinker.li
  2024-02-14  2:08 ` [RFC bpf-next v2 2/3] bpftool: generated shadow variables for struct_ops maps thinker.li
@ 2024-02-14  2:08 ` thinker.li
  2024-02-15 23:50 ` [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons Andrii Nakryiko
  3 siblings, 0 replies; 11+ messages in thread
From: thinker.li @ 2024-02-14  2:08 UTC (permalink / raw)
  To: bpf, ast, martin.lau, song, kernel-team, andrii
  Cc: sinquersw, kuifeng, Kui-Feng Lee

From: Kui-Feng Lee <thinker.li@gmail.com>

Change the value of fields, including scalar types and function pointers,
and check if the struct_ops map works as expected.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 .../selftests/bpf/bpf_testmod/bpf_testmod.c      |  6 +++++-
 .../selftests/bpf/bpf_testmod/bpf_testmod.h      |  1 +
 .../bpf/prog_tests/test_struct_ops_module.c      | 16 ++++++++++++----
 .../selftests/bpf/progs/struct_ops_module.c      |  8 ++++++++
 4 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index 66787e99ba1b..96fb0f44a390 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -539,6 +539,10 @@ static int bpf_testmod_ops_init_member(const struct btf_type *t,
 				       const struct btf_member *member,
 				       void *kdata, const void *udata)
 {
+	if (member->offset == offsetof(struct bpf_testmod_ops, data) * 8) {
+		((struct bpf_testmod_ops *)kdata)->data = ((struct bpf_testmod_ops *)udata)->data;
+		return 1;
+	}
 	return 0;
 }
 
@@ -559,7 +563,7 @@ static int bpf_dummy_reg(void *kdata)
 	 * initialized, so we need to check for NULL.
 	 */
 	if (ops->test_2)
-		ops->test_2(4, 3);
+		ops->test_2(4, ops->data);
 
 	return 0;
 }
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h
index c3b0cf788f9f..428efd65cafd 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h
@@ -35,6 +35,7 @@ struct bpf_testmod_ops {
 	void (*test_2)(int a, int b);
 	/* Used to test nullable arguments. */
 	int (*test_maybe_null)(int dummy, struct task_struct *task);
+	int data;
 };
 
 #endif /* _BPF_TESTMOD_H */
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
index 8d833f0c7580..68d91b769ca0 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
@@ -32,17 +32,20 @@ static void check_map_info(struct bpf_map_info *info)
 
 static void test_struct_ops_load(void)
 {
-	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
 	struct struct_ops_module *skel;
 	struct bpf_map_info info = {};
 	struct bpf_link *link;
 	int err;
 	u32 len;
 
-	skel = struct_ops_module__open_opts(&opts);
+	skel = struct_ops_module__open();
 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
 		return;
 
+	skel->struct_ops.testmod_1->data = 13;
+	skel->struct_ops.testmod_1->test_2 = skel->progs.test_3;
+	bpf_program__set_autoload(skel->progs.test_2, false);
+
 	err = struct_ops_module__load(skel);
 	if (!ASSERT_OK(err, "struct_ops_module_load"))
 		goto cleanup;
@@ -56,8 +59,13 @@ static void test_struct_ops_load(void)
 	link = bpf_map__attach_struct_ops(skel->maps.testmod_1);
 	ASSERT_OK_PTR(link, "attach_test_mod_1");
 
-	/* test_2() will be called from bpf_dummy_reg() in bpf_testmod.c */
-	ASSERT_EQ(skel->bss->test_2_result, 7, "test_2_result");
+	/* test_3() will be called from bpf_dummy_reg() in bpf_testmod.c
+	 *
+	 * In bpf_testmod.c it will pass 4 and 13 (the value of data) to
+	 * .test_2.  So, the value of test_2_result should be 20 (4 + 13 +
+	 * 3).
+	 */
+	ASSERT_EQ(skel->bss->test_2_result, 20, "test_2_result");
 
 	bpf_link__destroy(link);
 
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_module.c b/tools/testing/selftests/bpf/progs/struct_ops_module.c
index b78746b3cef3..25952fa09348 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_module.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_module.c
@@ -21,9 +21,17 @@ void BPF_PROG(test_2, int a, int b)
 	test_2_result = a + b;
 }
 
+SEC("struct_ops/test_3")
+int BPF_PROG(test_3, int a, int b)
+{
+	test_2_result = a + b + 3;
+	return a + b + 3;
+}
+
 SEC(".struct_ops.link")
 struct bpf_testmod_ops testmod_1 = {
 	.test_1 = (void *)test_1,
 	.test_2 = (void *)test_2,
+	.data = 0x1,
 };
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons
  2024-02-14  2:08 [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons thinker.li
                   ` (2 preceding siblings ...)
  2024-02-14  2:08 ` [RFC bpf-next v2 3/3] selftests/bpf: Test if shadow variables work thinker.li
@ 2024-02-15 23:50 ` Andrii Nakryiko
  2024-02-16  2:40   ` Kui-Feng Lee
  3 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2024-02-15 23:50 UTC (permalink / raw)
  To: thinker.li
  Cc: bpf, ast, martin.lau, song, kernel-team, andrii, sinquersw, kuifeng

On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
>
> From: Kui-Feng Lee <thinker.li@gmail.com>
>
> This RFC is for gathering feedback/opinions on the design.
> Based on the feedback received for v1, I made some modifications.
>
> == Pointers to Shadow Copies ==
>
> With the current implementation, the code generator will create a
> pointer to a shadow copy of the struct_ops map for each map. For
> instance, if we define a testmod_1 as a struct_ops map, we can access
> its corresponding shadow variable "data" using the pointer.
>
>     skel->struct_ops.testmod1->data
>
> == Shadow Info ==
>
> The code generator also generates a shadow info to describe the layout
> of the data pointed to by all these pointers. For instance, the
> following shadow info describes the layout of a struct_ops map called
> testmod_1, which has 3 members: test_1, test_2, and data.
>
>     static struct bpf_struct_ops_member_info member_info_testmod_1[] = {
>         {
>                 .name = "test_1",
>                 .offset = .....,
>                 .size = .....,
>         },
>         {
>                 .name = "test_2",
>                 .offset = .....,
>                 .size = .....,
>         },
>         {
>                 .name = "data",
>                 .offset = .....,
>                 .size = .....,
>         },
>     };
>     static struct bpf_struct_ops_map_info map_info[] = {
>         {
>                 .name = "testmod_1",
>                 .members = member_info_testmod_1,
>                 .cnt = ARRAY_SIZE(member_info_testmod_1),
>                 .data_size = sizeof(struct_ops->testmod_1),
>         },
>     };
>     static struct bpf_struct_ops_shadow_info shadow_info = {
>         .maps = map_info,
>         .cnt = ARRAY_SIZE(map_info),
>     };
>
> A shadow info describes the layout of the shadow copies of all
> struct_ops maps included in a skeleton. (Defined in *__shadow_info())

I must be missing something, but libbpf knows the layout of struct_ops
struct through BTF, why do we need all these descriptors?

>
> == libbpf Creates Shadow Copies ==
>
> This shadow info should be passed to bpf_object__open_skeleton() as a
> part of "opts" so that libbpf can create shadow copies with the layout
> described by the shadow info. For now, *__open() in the skeleton will
> automatically pass the shadow info to bpf_object__open_skeleton(),
> looking like the following example.
>
>     static inline struct struct_ops_module *
>     struct_ops_module__open(void)
>     {
>         DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
>
>         opts.struct_ops_shadow = struct_ops_module__shadow_info();
>
>         return struct_ops_module__open_opts(*** BLURB HERE ***opts);
>     }
>
> The function bpf_map__initial_value() will return the shadow copy that
> is created based on the received shadow info. Therefore, in the
> function *__open_opts() in the skeleton, the pointers to shadow copies
> will be initialized with the values returned from
> bpf_map__initial_value(). For instance,
>
>    obj->struct_ops.testmod_1 =
>         bpf_map__initial_value(obj->maps.testmod_1, NULL);
>

I also don't get why you need to allocate some extra "shadow memory"
if we already have struct bpf_struct_ops->data pointer malloc()'ed
during bpf_map initialization, and its size matches exactly the
struct_ops's type size.

> This line of code will be included in the *__open_opts() function. If
> the opts.struct_ops_shadow is not set, bpf_map__initial_value() will
> return a NULL.
>
> ========================================
> DESCRIPTION form v1
> ========================================
>

you probably don't need to keep cover letter from previous versions if
they are not relevant anymore

[...]

>
> ---
>
> v1: https://lore.kernel.org/all/20240124224130.859921-1-thinker.li@gmail.com/
>
> Kui-Feng Lee (3):
>   libbpf: Create a shadow copy for each struct_ops map if necessary.
>   bpftool: generated shadow variables for struct_ops maps.
>   selftests/bpf: Test if shadow variables work.
>
>  tools/bpf/bpftool/gen.c                       | 358 +++++++++++++++++-
>  tools/lib/bpf/libbpf.c                        | 195 +++++++++-
>  tools/lib/bpf/libbpf.h                        |  34 +-
>  tools/lib/bpf/libbpf.map                      |   1 +
>  tools/lib/bpf/libbpf_internal.h               |   1 +
>  .../selftests/bpf/bpf_testmod/bpf_testmod.c   |   6 +-
>  .../selftests/bpf/bpf_testmod/bpf_testmod.h   |   1 +
>  .../bpf/prog_tests/test_struct_ops_module.c   |  16 +-
>  .../selftests/bpf/progs/struct_ops_module.c   |   8 +
>  9 files changed, 596 insertions(+), 24 deletions(-)
>
> --
> 2.34.1
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary.
  2024-02-14  2:08 ` [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary thinker.li
@ 2024-02-15 23:55   ` Andrii Nakryiko
  2024-02-16  2:35     ` Kui-Feng Lee
  0 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2024-02-15 23:55 UTC (permalink / raw)
  To: thinker.li
  Cc: bpf, ast, martin.lau, song, kernel-team, andrii, sinquersw, kuifeng

On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
>
> From: Kui-Feng Lee <thinker.li@gmail.com>
>
> If the user has passed a shadow info for a struct_ops map along with struct
> bpf_object_open_opts, a shadow copy will be created for the map and
> returned from bpf_map__initial_value().
>
> The user can read and write shadow variables through the shadow copy, which
> is placed in the struct pointed by skel->struct_ops.FOO, where FOO is the
> map name.
>
> The value of a shadow variable will be used to update the value of the map
> when loading the map to the kernel.
>
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
>  tools/lib/bpf/libbpf.c          | 195 ++++++++++++++++++++++++++++++--
>  tools/lib/bpf/libbpf.h          |  34 +++++-
>  tools/lib/bpf/libbpf.map        |   1 +
>  tools/lib/bpf/libbpf_internal.h |   1 +
>  4 files changed, 220 insertions(+), 11 deletions(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 01f407591a92..ce9c4cdb2dc5 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -487,6 +487,14 @@ struct bpf_struct_ops {
>          * from "data".
>          */
>         void *kern_vdata;
> +       /* Description of the layout that a shadow copy should look like.
> +        */
> +       const struct bpf_struct_ops_map_info *shadow_info;
> +       /* A shadow copy of the struct_ops data created according to the
> +        * layout described by shadow_info.
> +        */
> +       void *shadow_data;
> +       __u32 shadow_data_size;

what I mentioned on cover letter, just a few lines above, before
kern_vdata we have just `void *data` which initially contains whatever
was set in ELF. Just expose that through bpf_map__initial_value() and
teach bpftool to generate section with variables for that memory and
that should be all we need, no?

>         __u32 type_id;
>  };
>
> @@ -1027,7 +1035,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
>         struct module_btf *mod_btf;
>         void *data, *kern_data;
>         const char *tname;
> -       int err;
> +       int err, j;
>
>         st_ops = map->st_ops;
>         type = st_ops->type;

[...]

>  void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
>  {
> +       if (bpf_map__is_struct_ops(map)) {
> +               if (psize)
> +                       *psize = map->st_ops->shadow_data_size;
> +               return map->st_ops->shadow_data;
> +       }
> +
>         if (!map->mmaped)
>                 return NULL;
>         *psize = map->def.value_size;
> @@ -13462,3 +13632,8 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
>         free(s->progs);
>         free(s);
>  }
> +
> +__u32 bpf_map__struct_ops_type(const struct bpf_map *map)
> +{
> +       return map->st_ops->type_id;
> +}

we can expose this st_ops->type_id as map->def.value_type_id so that
existing bpf_map__btf_value_type_id() API can be used, no need to add
more struct_ops-specific APIs

> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index 5723cbbfcc41..b435cafefe7a 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -109,6 +109,27 @@ LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn);
>  /* Hide internal to user */
>  struct bpf_object;
>
> +/* Description of a member in the struct_ops type for a map. */
> +struct bpf_struct_ops_member_info {
> +       const char *name;
> +       __u32 offset;
> +       __u32 size;
> +};
> +
> +/* Description of the layout of a shadow copy for a struct_ops map. */
> +struct bpf_struct_ops_map_info {
> +       /* The name of the struct_ops map */
> +       const char *name;
> +       const struct bpf_struct_ops_member_info *members;
> +       __u32 cnt;
> +       __u32 data_size;
> +};
> +
> +struct bpf_struct_ops_shadow_info {
> +       const struct bpf_struct_ops_map_info *maps;
> +       __u32 cnt;
> +};
> +
>  struct bpf_object_open_opts {
>         /* size of this struct, for forward/backward compatibility */
>         size_t sz;
> @@ -197,9 +218,18 @@ struct bpf_object_open_opts {
>          */
>         const char *bpf_token_path;
>
> +       /* A list of shadow info for every struct_ops map.  A shadow info
> +        * provides the information used by libbpf to map the offsets of
> +        * struct members of a struct_ops type from BTF to the offsets of
> +        * the corresponding members in the shadow copy in the user
> +        * space. It ensures that the shadow copy provided by the libbpf
> +        * can be accessed by the user space program correctly.
> +        */
> +       const struct bpf_struct_ops_shadow_info *struct_ops_shadow;
> +

I still don't follow. bpftool will generate memory-layout compatible
structure for user-space, they can just work directly with that
memory. We shouldn't need all this extra info structs.

Libbpf can just check that fields that are supposed to be BPF prog
references are correct `struct bpf_program *` pointers.

>         size_t :0;
>  };
> -#define bpf_object_open_opts__last_field bpf_token_path
> +#define bpf_object_open_opts__last_field struct_ops_shadow
>
>  /**
>   * @brief **bpf_object__open()** creates a bpf_object by opening
> @@ -839,6 +869,8 @@ struct bpf_map;
>  LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
>  LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
>
> +LIBBPF_API __u32 bpf_map__struct_ops_type(const struct bpf_map *map);
> +
>  struct bpf_iter_attach_opts {
>         size_t sz; /* size of this struct for forward/backward compatibility */
>         union bpf_iter_link_info *link_info;
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 86804fd90dd1..e0efc85114df 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -413,4 +413,5 @@ LIBBPF_1.4.0 {
>                 bpf_token_create;
>                 btf__new_split;
>                 btf_ext__raw_data;
> +               bpf_map__struct_ops_type;
>  } LIBBPF_1.3.0;
> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> index ad936ac5e639..aec6d57fe5d1 100644
> --- a/tools/lib/bpf/libbpf_internal.h
> +++ b/tools/lib/bpf/libbpf_internal.h
> @@ -234,6 +234,7 @@ struct btf_type;
>  struct btf_type *btf_type_by_id(const struct btf *btf, __u32 type_id);
>  const char *btf_kind_str(const struct btf_type *t);
>  const struct btf_type *skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
> +const struct btf_type *resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id);
>
>  static inline enum btf_func_linkage btf_func_linkage(const struct btf_type *t)
>  {
> --
> 2.34.1
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary.
  2024-02-15 23:55   ` Andrii Nakryiko
@ 2024-02-16  2:35     ` Kui-Feng Lee
  2024-02-16 16:52       ` Andrii Nakryiko
  0 siblings, 1 reply; 11+ messages in thread
From: Kui-Feng Lee @ 2024-02-16  2:35 UTC (permalink / raw)
  To: Andrii Nakryiko, thinker.li
  Cc: bpf, ast, martin.lau, song, kernel-team, andrii, kuifeng



On 2/15/24 15:55, Andrii Nakryiko wrote:
> On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
>>
>> From: Kui-Feng Lee <thinker.li@gmail.com>
>>
>> If the user has passed a shadow info for a struct_ops map along with struct
>> bpf_object_open_opts, a shadow copy will be created for the map and
>> returned from bpf_map__initial_value().
>>
>> The user can read and write shadow variables through the shadow copy, which
>> is placed in the struct pointed by skel->struct_ops.FOO, where FOO is the
>> map name.
>>
>> The value of a shadow variable will be used to update the value of the map
>> when loading the map to the kernel.
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>>   tools/lib/bpf/libbpf.c          | 195 ++++++++++++++++++++++++++++++--
>>   tools/lib/bpf/libbpf.h          |  34 +++++-
>>   tools/lib/bpf/libbpf.map        |   1 +
>>   tools/lib/bpf/libbpf_internal.h |   1 +
>>   4 files changed, 220 insertions(+), 11 deletions(-)
>>
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index 01f407591a92..ce9c4cdb2dc5 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -487,6 +487,14 @@ struct bpf_struct_ops {
>>           * from "data".
>>           */
>>          void *kern_vdata;
>> +       /* Description of the layout that a shadow copy should look like.
>> +        */
>> +       const struct bpf_struct_ops_map_info *shadow_info;
>> +       /* A shadow copy of the struct_ops data created according to the
>> +        * layout described by shadow_info.
>> +        */
>> +       void *shadow_data;
>> +       __u32 shadow_data_size;
> 
> what I mentioned on cover letter, just a few lines above, before
> kern_vdata we have just `void *data` which initially contains whatever
> was set in ELF. Just expose that through bpf_map__initial_value() and
> teach bpftool to generate section with variables for that memory and
> that should be all we need, no?

I am not sure if read your question correctly.
Padding & alignments can vary in different platforms. BPF and
user space programs are supposed to be in different platforms.
So, I can not expect that the same struct has the same layout in
BPF/x86/and ARM, right?

> 
>>          __u32 type_id;
>>   };
>>
>> @@ -1027,7 +1035,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
>>          struct module_btf *mod_btf;
>>          void *data, *kern_data;
>>          const char *tname;
>> -       int err;
>> +       int err, j;
>>
>>          st_ops = map->st_ops;
>>          type = st_ops->type;
> 
> [...]
> 
>>   void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
>>   {
>> +       if (bpf_map__is_struct_ops(map)) {
>> +               if (psize)
>> +                       *psize = map->st_ops->shadow_data_size;
>> +               return map->st_ops->shadow_data;
>> +       }
>> +
>>          if (!map->mmaped)
>>                  return NULL;
>>          *psize = map->def.value_size;
>> @@ -13462,3 +13632,8 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
>>          free(s->progs);
>>          free(s);
>>   }
>> +
>> +__u32 bpf_map__struct_ops_type(const struct bpf_map *map)
>> +{
>> +       return map->st_ops->type_id;
>> +}
> 
> we can expose this st_ops->type_id as map->def.value_type_id so that
> existing bpf_map__btf_value_type_id() API can be used, no need to add
> more struct_ops-specific APIs

OK!

> 
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index 5723cbbfcc41..b435cafefe7a 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -109,6 +109,27 @@ LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn);
>>   /* Hide internal to user */
>>   struct bpf_object;
>>
>> +/* Description of a member in the struct_ops type for a map. */
>> +struct bpf_struct_ops_member_info {
>> +       const char *name;
>> +       __u32 offset;
>> +       __u32 size;
>> +};
>> +
>> +/* Description of the layout of a shadow copy for a struct_ops map. */
>> +struct bpf_struct_ops_map_info {
>> +       /* The name of the struct_ops map */
>> +       const char *name;
>> +       const struct bpf_struct_ops_member_info *members;
>> +       __u32 cnt;
>> +       __u32 data_size;
>> +};
>> +
>> +struct bpf_struct_ops_shadow_info {
>> +       const struct bpf_struct_ops_map_info *maps;
>> +       __u32 cnt;
>> +};
>> +
>>   struct bpf_object_open_opts {
>>          /* size of this struct, for forward/backward compatibility */
>>          size_t sz;
>> @@ -197,9 +218,18 @@ struct bpf_object_open_opts {
>>           */
>>          const char *bpf_token_path;
>>
>> +       /* A list of shadow info for every struct_ops map.  A shadow info
>> +        * provides the information used by libbpf to map the offsets of
>> +        * struct members of a struct_ops type from BTF to the offsets of
>> +        * the corresponding members in the shadow copy in the user
>> +        * space. It ensures that the shadow copy provided by the libbpf
>> +        * can be accessed by the user space program correctly.
>> +        */
>> +       const struct bpf_struct_ops_shadow_info *struct_ops_shadow;
>> +
> 
> I still don't follow. bpftool will generate memory-layout compatible
> structure for user-space, they can just work directly with that
> memory. We shouldn't need all this extra info structs.
> 
> Libbpf can just check that fields that are supposed to be BPF prog
> references are correct `struct bpf_program *` pointers.

Check the explanation above.

> 
>>          size_t :0;
>>   };
>> -#define bpf_object_open_opts__last_field bpf_token_path
>> +#define bpf_object_open_opts__last_field struct_ops_shadow
>>
>>   /**
>>    * @brief **bpf_object__open()** creates a bpf_object by opening
>> @@ -839,6 +869,8 @@ struct bpf_map;
>>   LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
>>   LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
>>
>> +LIBBPF_API __u32 bpf_map__struct_ops_type(const struct bpf_map *map);
>> +
>>   struct bpf_iter_attach_opts {
>>          size_t sz; /* size of this struct for forward/backward compatibility */
>>          union bpf_iter_link_info *link_info;
>> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
>> index 86804fd90dd1..e0efc85114df 100644
>> --- a/tools/lib/bpf/libbpf.map
>> +++ b/tools/lib/bpf/libbpf.map
>> @@ -413,4 +413,5 @@ LIBBPF_1.4.0 {
>>                  bpf_token_create;
>>                  btf__new_split;
>>                  btf_ext__raw_data;
>> +               bpf_map__struct_ops_type;
>>   } LIBBPF_1.3.0;
>> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
>> index ad936ac5e639..aec6d57fe5d1 100644
>> --- a/tools/lib/bpf/libbpf_internal.h
>> +++ b/tools/lib/bpf/libbpf_internal.h
>> @@ -234,6 +234,7 @@ struct btf_type;
>>   struct btf_type *btf_type_by_id(const struct btf *btf, __u32 type_id);
>>   const char *btf_kind_str(const struct btf_type *t);
>>   const struct btf_type *skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
>> +const struct btf_type *resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id);
>>
>>   static inline enum btf_func_linkage btf_func_linkage(const struct btf_type *t)
>>   {
>> --
>> 2.34.1
>>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons
  2024-02-15 23:50 ` [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons Andrii Nakryiko
@ 2024-02-16  2:40   ` Kui-Feng Lee
  2024-02-16 16:54     ` Andrii Nakryiko
  0 siblings, 1 reply; 11+ messages in thread
From: Kui-Feng Lee @ 2024-02-16  2:40 UTC (permalink / raw)
  To: Andrii Nakryiko, thinker.li
  Cc: bpf, ast, martin.lau, song, kernel-team, andrii, kuifeng



On 2/15/24 15:50, Andrii Nakryiko wrote:
> On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
>>
>> From: Kui-Feng Lee <thinker.li@gmail.com>
>>
>> This RFC is for gathering feedback/opinions on the design.
>> Based on the feedback received for v1, I made some modifications.
>>
>> == Pointers to Shadow Copies ==
>>
>> With the current implementation, the code generator will create a
>> pointer to a shadow copy of the struct_ops map for each map. For
>> instance, if we define a testmod_1 as a struct_ops map, we can access
>> its corresponding shadow variable "data" using the pointer.
>>
>>      skel->struct_ops.testmod1->data
>>
>> == Shadow Info ==
>>
>> The code generator also generates a shadow info to describe the layout
>> of the data pointed to by all these pointers. For instance, the
>> following shadow info describes the layout of a struct_ops map called
>> testmod_1, which has 3 members: test_1, test_2, and data.
>>
>>      static struct bpf_struct_ops_member_info member_info_testmod_1[] = {
>>          {
>>                  .name = "test_1",
>>                  .offset = .....,
>>                  .size = .....,
>>          },
>>          {
>>                  .name = "test_2",
>>                  .offset = .....,
>>                  .size = .....,
>>          },
>>          {
>>                  .name = "data",
>>                  .offset = .....,
>>                  .size = .....,
>>          },
>>      };
>>      static struct bpf_struct_ops_map_info map_info[] = {
>>          {
>>                  .name = "testmod_1",
>>                  .members = member_info_testmod_1,
>>                  .cnt = ARRAY_SIZE(member_info_testmod_1),
>>                  .data_size = sizeof(struct_ops->testmod_1),
>>          },
>>      };
>>      static struct bpf_struct_ops_shadow_info shadow_info = {
>>          .maps = map_info,
>>          .cnt = ARRAY_SIZE(map_info),
>>      };
>>
>> A shadow info describes the layout of the shadow copies of all
>> struct_ops maps included in a skeleton. (Defined in *__shadow_info())
> 
> I must be missing something, but libbpf knows the layout of struct_ops
> struct through BTF, why do we need all these descriptors?

I explain it in the response for part 1.

> 
>>
>> == libbpf Creates Shadow Copies ==
>>
>> This shadow info should be passed to bpf_object__open_skeleton() as a
>> part of "opts" so that libbpf can create shadow copies with the layout
>> described by the shadow info. For now, *__open() in the skeleton will
>> automatically pass the shadow info to bpf_object__open_skeleton(),
>> looking like the following example.
>>
>>      static inline struct struct_ops_module *
>>      struct_ops_module__open(void)
>>      {
>>          DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
>>
>>          opts.struct_ops_shadow = struct_ops_module__shadow_info();
>>
>>          return struct_ops_module__open_opts(*** BLURB HERE ***opts);
>>      }
>>
>> The function bpf_map__initial_value() will return the shadow copy that
>> is created based on the received shadow info. Therefore, in the
>> function *__open_opts() in the skeleton, the pointers to shadow copies
>> will be initialized with the values returned from
>> bpf_map__initial_value(). For instance,
>>
>>     obj->struct_ops.testmod_1 =
>>          bpf_map__initial_value(obj->maps.testmod_1, NULL);
>>
> 
> I also don't get why you need to allocate some extra "shadow memory"
> if we already have struct bpf_struct_ops->data pointer malloc()'ed
> during bpf_map initialization, and its size matches exactly the
> struct_ops's type size.


I assume that the alignments & padding of BPF and the user space
programs are different. (Check the response for part 1 as well)

> 
>> This line of code will be included in the *__open_opts() function. If
>> the opts.struct_ops_shadow is not set, bpf_map__initial_value() will
>> return a NULL.
>>
>> ========================================
>> DESCRIPTION form v1
>> ========================================
>>
> 
> you probably don't need to keep cover letter from previous versions if
> they are not relevant anymore
> 
> [...]


Sure!
It explains what the feature is and how to use this feature.
So, I keep it here for people just found this discussion.

> 
>>
>> ---
>>
>> v1: https://lore.kernel.org/all/20240124224130.859921-1-thinker.li@gmail.com/
>>
>> Kui-Feng Lee (3):
>>    libbpf: Create a shadow copy for each struct_ops map if necessary.
>>    bpftool: generated shadow variables for struct_ops maps.
>>    selftests/bpf: Test if shadow variables work.
>>
>>   tools/bpf/bpftool/gen.c                       | 358 +++++++++++++++++-
>>   tools/lib/bpf/libbpf.c                        | 195 +++++++++-
>>   tools/lib/bpf/libbpf.h                        |  34 +-
>>   tools/lib/bpf/libbpf.map                      |   1 +
>>   tools/lib/bpf/libbpf_internal.h               |   1 +
>>   .../selftests/bpf/bpf_testmod/bpf_testmod.c   |   6 +-
>>   .../selftests/bpf/bpf_testmod/bpf_testmod.h   |   1 +
>>   .../bpf/prog_tests/test_struct_ops_module.c   |  16 +-
>>   .../selftests/bpf/progs/struct_ops_module.c   |   8 +
>>   9 files changed, 596 insertions(+), 24 deletions(-)
>>
>> --
>> 2.34.1
>>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary.
  2024-02-16  2:35     ` Kui-Feng Lee
@ 2024-02-16 16:52       ` Andrii Nakryiko
  2024-02-16 17:12         ` Kui-Feng Lee
  0 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2024-02-16 16:52 UTC (permalink / raw)
  To: Kui-Feng Lee
  Cc: thinker.li, bpf, ast, martin.lau, song, kernel-team, andrii, kuifeng

On Thu, Feb 15, 2024 at 6:35 PM Kui-Feng Lee <sinquersw@gmail.com> wrote:
>
>
>
> On 2/15/24 15:55, Andrii Nakryiko wrote:
> > On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
> >>
> >> From: Kui-Feng Lee <thinker.li@gmail.com>
> >>
> >> If the user has passed a shadow info for a struct_ops map along with struct
> >> bpf_object_open_opts, a shadow copy will be created for the map and
> >> returned from bpf_map__initial_value().
> >>
> >> The user can read and write shadow variables through the shadow copy, which
> >> is placed in the struct pointed by skel->struct_ops.FOO, where FOO is the
> >> map name.
> >>
> >> The value of a shadow variable will be used to update the value of the map
> >> when loading the map to the kernel.
> >>
> >> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> >> ---
> >>   tools/lib/bpf/libbpf.c          | 195 ++++++++++++++++++++++++++++++--
> >>   tools/lib/bpf/libbpf.h          |  34 +++++-
> >>   tools/lib/bpf/libbpf.map        |   1 +
> >>   tools/lib/bpf/libbpf_internal.h |   1 +
> >>   4 files changed, 220 insertions(+), 11 deletions(-)
> >>
> >> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> >> index 01f407591a92..ce9c4cdb2dc5 100644
> >> --- a/tools/lib/bpf/libbpf.c
> >> +++ b/tools/lib/bpf/libbpf.c
> >> @@ -487,6 +487,14 @@ struct bpf_struct_ops {
> >>           * from "data".
> >>           */
> >>          void *kern_vdata;
> >> +       /* Description of the layout that a shadow copy should look like.
> >> +        */
> >> +       const struct bpf_struct_ops_map_info *shadow_info;
> >> +       /* A shadow copy of the struct_ops data created according to the
> >> +        * layout described by shadow_info.
> >> +        */
> >> +       void *shadow_data;
> >> +       __u32 shadow_data_size;
> >
> > what I mentioned on cover letter, just a few lines above, before
> > kern_vdata we have just `void *data` which initially contains whatever
> > was set in ELF. Just expose that through bpf_map__initial_value() and
> > teach bpftool to generate section with variables for that memory and
> > that should be all we need, no?
>
> I am not sure if read your question correctly.
> Padding & alignments can vary in different platforms. BPF and
> user space programs are supposed to be in different platforms.
> So, I can not expect that the same struct has the same layout in
> BPF/x86/and ARM, right?

We can constraint this functionality to 64-bit host architectures, and
then all these concerns will go away. It should be possible to make
all this work even if the host architecture is 64-bit, but I'm not
sure it's worth doing.

Either way, we need to keep this simple and minimal, no extra
descriptors and stuff like that.

>
> >
> >>          __u32 type_id;
> >>   };
> >>
> >> @@ -1027,7 +1035,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
> >>          struct module_btf *mod_btf;
> >>          void *data, *kern_data;
> >>          const char *tname;
> >> -       int err;
> >> +       int err, j;
> >>
> >>          st_ops = map->st_ops;
> >>          type = st_ops->type;
> >
> > [...]
> >
> >>   void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
> >>   {
> >> +       if (bpf_map__is_struct_ops(map)) {
> >> +               if (psize)
> >> +                       *psize = map->st_ops->shadow_data_size;
> >> +               return map->st_ops->shadow_data;
> >> +       }
> >> +
> >>          if (!map->mmaped)
> >>                  return NULL;
> >>          *psize = map->def.value_size;
> >> @@ -13462,3 +13632,8 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
> >>          free(s->progs);
> >>          free(s);
> >>   }
> >> +
> >> +__u32 bpf_map__struct_ops_type(const struct bpf_map *map)
> >> +{
> >> +       return map->st_ops->type_id;
> >> +}
> >
> > we can expose this st_ops->type_id as map->def.value_type_id so that
> > existing bpf_map__btf_value_type_id() API can be used, no need to add
> > more struct_ops-specific APIs
>
> OK!
>
> >
> >> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> >> index 5723cbbfcc41..b435cafefe7a 100644
> >> --- a/tools/lib/bpf/libbpf.h
> >> +++ b/tools/lib/bpf/libbpf.h
> >> @@ -109,6 +109,27 @@ LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn);
> >>   /* Hide internal to user */
> >>   struct bpf_object;
> >>
> >> +/* Description of a member in the struct_ops type for a map. */
> >> +struct bpf_struct_ops_member_info {
> >> +       const char *name;
> >> +       __u32 offset;
> >> +       __u32 size;
> >> +};
> >> +
> >> +/* Description of the layout of a shadow copy for a struct_ops map. */
> >> +struct bpf_struct_ops_map_info {
> >> +       /* The name of the struct_ops map */
> >> +       const char *name;
> >> +       const struct bpf_struct_ops_member_info *members;
> >> +       __u32 cnt;
> >> +       __u32 data_size;
> >> +};
> >> +
> >> +struct bpf_struct_ops_shadow_info {
> >> +       const struct bpf_struct_ops_map_info *maps;
> >> +       __u32 cnt;
> >> +};
> >> +
> >>   struct bpf_object_open_opts {
> >>          /* size of this struct, for forward/backward compatibility */
> >>          size_t sz;
> >> @@ -197,9 +218,18 @@ struct bpf_object_open_opts {
> >>           */
> >>          const char *bpf_token_path;
> >>
> >> +       /* A list of shadow info for every struct_ops map.  A shadow info
> >> +        * provides the information used by libbpf to map the offsets of
> >> +        * struct members of a struct_ops type from BTF to the offsets of
> >> +        * the corresponding members in the shadow copy in the user
> >> +        * space. It ensures that the shadow copy provided by the libbpf
> >> +        * can be accessed by the user space program correctly.
> >> +        */
> >> +       const struct bpf_struct_ops_shadow_info *struct_ops_shadow;
> >> +
> >
> > I still don't follow. bpftool will generate memory-layout compatible
> > structure for user-space, they can just work directly with that
> > memory. We shouldn't need all this extra info structs.
> >
> > Libbpf can just check that fields that are supposed to be BPF prog
> > references are correct `struct bpf_program *` pointers.
>
> Check the explanation above.
>
> >
> >>          size_t :0;
> >>   };
> >> -#define bpf_object_open_opts__last_field bpf_token_path
> >> +#define bpf_object_open_opts__last_field struct_ops_shadow
> >>
> >>   /**
> >>    * @brief **bpf_object__open()** creates a bpf_object by opening
> >> @@ -839,6 +869,8 @@ struct bpf_map;
> >>   LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
> >>   LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
> >>
> >> +LIBBPF_API __u32 bpf_map__struct_ops_type(const struct bpf_map *map);
> >> +
> >>   struct bpf_iter_attach_opts {
> >>          size_t sz; /* size of this struct for forward/backward compatibility */
> >>          union bpf_iter_link_info *link_info;
> >> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> >> index 86804fd90dd1..e0efc85114df 100644
> >> --- a/tools/lib/bpf/libbpf.map
> >> +++ b/tools/lib/bpf/libbpf.map
> >> @@ -413,4 +413,5 @@ LIBBPF_1.4.0 {
> >>                  bpf_token_create;
> >>                  btf__new_split;
> >>                  btf_ext__raw_data;
> >> +               bpf_map__struct_ops_type;
> >>   } LIBBPF_1.3.0;
> >> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> >> index ad936ac5e639..aec6d57fe5d1 100644
> >> --- a/tools/lib/bpf/libbpf_internal.h
> >> +++ b/tools/lib/bpf/libbpf_internal.h
> >> @@ -234,6 +234,7 @@ struct btf_type;
> >>   struct btf_type *btf_type_by_id(const struct btf *btf, __u32 type_id);
> >>   const char *btf_kind_str(const struct btf_type *t);
> >>   const struct btf_type *skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
> >> +const struct btf_type *resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id);
> >>
> >>   static inline enum btf_func_linkage btf_func_linkage(const struct btf_type *t)
> >>   {
> >> --
> >> 2.34.1
> >>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons
  2024-02-16  2:40   ` Kui-Feng Lee
@ 2024-02-16 16:54     ` Andrii Nakryiko
  0 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-02-16 16:54 UTC (permalink / raw)
  To: Kui-Feng Lee
  Cc: thinker.li, bpf, ast, martin.lau, song, kernel-team, andrii, kuifeng

On Thu, Feb 15, 2024 at 6:40 PM Kui-Feng Lee <sinquersw@gmail.com> wrote:
>
>
>
> On 2/15/24 15:50, Andrii Nakryiko wrote:
> > On Tue, Feb 13, 2024 at 6:08 PM <thinker.li@gmail.com> wrote:
> >>
> >> From: Kui-Feng Lee <thinker.li@gmail.com>
> >>
> >> This RFC is for gathering feedback/opinions on the design.
> >> Based on the feedback received for v1, I made some modifications.
> >>
> >> == Pointers to Shadow Copies ==
> >>
> >> With the current implementation, the code generator will create a
> >> pointer to a shadow copy of the struct_ops map for each map. For
> >> instance, if we define a testmod_1 as a struct_ops map, we can access
> >> its corresponding shadow variable "data" using the pointer.
> >>
> >>      skel->struct_ops.testmod1->data
> >>
> >> == Shadow Info ==
> >>
> >> The code generator also generates a shadow info to describe the layout
> >> of the data pointed to by all these pointers. For instance, the
> >> following shadow info describes the layout of a struct_ops map called
> >> testmod_1, which has 3 members: test_1, test_2, and data.
> >>
> >>      static struct bpf_struct_ops_member_info member_info_testmod_1[] = {
> >>          {
> >>                  .name = "test_1",
> >>                  .offset = .....,
> >>                  .size = .....,
> >>          },
> >>          {
> >>                  .name = "test_2",
> >>                  .offset = .....,
> >>                  .size = .....,
> >>          },
> >>          {
> >>                  .name = "data",
> >>                  .offset = .....,
> >>                  .size = .....,
> >>          },
> >>      };
> >>      static struct bpf_struct_ops_map_info map_info[] = {
> >>          {
> >>                  .name = "testmod_1",
> >>                  .members = member_info_testmod_1,
> >>                  .cnt = ARRAY_SIZE(member_info_testmod_1),
> >>                  .data_size = sizeof(struct_ops->testmod_1),
> >>          },
> >>      };
> >>      static struct bpf_struct_ops_shadow_info shadow_info = {
> >>          .maps = map_info,
> >>          .cnt = ARRAY_SIZE(map_info),
> >>      };
> >>
> >> A shadow info describes the layout of the shadow copies of all
> >> struct_ops maps included in a skeleton. (Defined in *__shadow_info())
> >
> > I must be missing something, but libbpf knows the layout of struct_ops
> > struct through BTF, why do we need all these descriptors?
>
> I explain it in the response for part 1.

yep, replied there. Let's keep it simple and restrict this
functionality to 64-bit host architecture

>
> >
> >>
> >> == libbpf Creates Shadow Copies ==
> >>
> >> This shadow info should be passed to bpf_object__open_skeleton() as a
> >> part of "opts" so that libbpf can create shadow copies with the layout
> >> described by the shadow info. For now, *__open() in the skeleton will
> >> automatically pass the shadow info to bpf_object__open_skeleton(),
> >> looking like the following example.
> >>
> >>      static inline struct struct_ops_module *
> >>      struct_ops_module__open(void)
> >>      {
> >>          DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
> >>
> >>          opts.struct_ops_shadow = struct_ops_module__shadow_info();
> >>
> >>          return struct_ops_module__open_opts(*** BLURB HERE ***opts);
> >>      }
> >>
> >> The function bpf_map__initial_value() will return the shadow copy that
> >> is created based on the received shadow info. Therefore, in the
> >> function *__open_opts() in the skeleton, the pointers to shadow copies
> >> will be initialized with the values returned from
> >> bpf_map__initial_value(). For instance,
> >>
> >>     obj->struct_ops.testmod_1 =
> >>          bpf_map__initial_value(obj->maps.testmod_1, NULL);
> >>
> >
> > I also don't get why you need to allocate some extra "shadow memory"
> > if we already have struct bpf_struct_ops->data pointer malloc()'ed
> > during bpf_map initialization, and its size matches exactly the
> > struct_ops's type size.
>
>
> I assume that the alignments & padding of BPF and the user space
> programs are different. (Check the response for part 1 as well)
>
> >
> >> This line of code will be included in the *__open_opts() function. If
> >> the opts.struct_ops_shadow is not set, bpf_map__initial_value() will
> >> return a NULL.
> >>
> >> ========================================
> >> DESCRIPTION form v1
> >> ========================================
> >>
> >
> > you probably don't need to keep cover letter from previous versions if
> > they are not relevant anymore
> >
> > [...]
>
>
> Sure!
> It explains what the feature is and how to use this feature.
> So, I keep it here for people just found this discussion.
>

Just lore link to the previous revision should be enough if anyone is
interested in history. Cover letter should represent the current state
of things.

> >
> >>
> >> ---
> >>
> >> v1: https://lore.kernel.org/all/20240124224130.859921-1-thinker.li@gmail.com/
> >>
> >> Kui-Feng Lee (3):
> >>    libbpf: Create a shadow copy for each struct_ops map if necessary.
> >>    bpftool: generated shadow variables for struct_ops maps.
> >>    selftests/bpf: Test if shadow variables work.
> >>
> >>   tools/bpf/bpftool/gen.c                       | 358 +++++++++++++++++-
> >>   tools/lib/bpf/libbpf.c                        | 195 +++++++++-
> >>   tools/lib/bpf/libbpf.h                        |  34 +-
> >>   tools/lib/bpf/libbpf.map                      |   1 +
> >>   tools/lib/bpf/libbpf_internal.h               |   1 +
> >>   .../selftests/bpf/bpf_testmod/bpf_testmod.c   |   6 +-
> >>   .../selftests/bpf/bpf_testmod/bpf_testmod.h   |   1 +
> >>   .../bpf/prog_tests/test_struct_ops_module.c   |  16 +-
> >>   .../selftests/bpf/progs/struct_ops_module.c   |   8 +
> >>   9 files changed, 596 insertions(+), 24 deletions(-)
> >>
> >> --
> >> 2.34.1
> >>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary.
  2024-02-16 16:52       ` Andrii Nakryiko
@ 2024-02-16 17:12         ` Kui-Feng Lee
  0 siblings, 0 replies; 11+ messages in thread
From: Kui-Feng Lee @ 2024-02-16 17:12 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: thinker.li, bpf, ast, martin.lau, song, kernel-team, andrii, kuifeng



On 2/16/24 08:52, Andrii Nakryiko wrote:
>>>> @@ -487,6 +487,14 @@ struct bpf_struct_ops {
>>>>            * from "data".
>>>>            */
>>>>           void *kern_vdata;
>>>> +       /* Description of the layout that a shadow copy should look like.
>>>> +        */
>>>> +       const struct bpf_struct_ops_map_info *shadow_info;
>>>> +       /* A shadow copy of the struct_ops data created according to the
>>>> +        * layout described by shadow_info.
>>>> +        */
>>>> +       void *shadow_data;
>>>> +       __u32 shadow_data_size;
>>> what I mentioned on cover letter, just a few lines above, before
>>> kern_vdata we have just `void *data` which initially contains whatever
>>> was set in ELF. Just expose that through bpf_map__initial_value() and
>>> teach bpftool to generate section with variables for that memory and
>>> that should be all we need, no?
>> I am not sure if read your question correctly.
>> Padding & alignments can vary in different platforms. BPF and
>> user space programs are supposed to be in different platforms.
>> So, I can not expect that the same struct has the same layout in
>> BPF/x86/and ARM, right?
> We can constraint this functionality to 64-bit host architectures, and
> then all these concerns will go away. It should be possible to make
> all this work even if the host architecture is 64-bit, but I'm not
> sure it's worth doing.
> 
> Either way, we need to keep this simple and minimal, no extra
> descriptors and stuff like that.
> 

Ok! I will make changes in the next version base on the assumption that
the host architecture is compatible with BPF.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2024-02-16 17:12 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-14  2:08 [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons thinker.li
2024-02-14  2:08 ` [RFC bpf-next v2 1/3] libbpf: Create a shadow copy for each struct_ops map if necessary thinker.li
2024-02-15 23:55   ` Andrii Nakryiko
2024-02-16  2:35     ` Kui-Feng Lee
2024-02-16 16:52       ` Andrii Nakryiko
2024-02-16 17:12         ` Kui-Feng Lee
2024-02-14  2:08 ` [RFC bpf-next v2 2/3] bpftool: generated shadow variables for struct_ops maps thinker.li
2024-02-14  2:08 ` [RFC bpf-next v2 3/3] selftests/bpf: Test if shadow variables work thinker.li
2024-02-15 23:50 ` [RFC bpf-next v2 0/3] Create shadow variables for struct_ops in skeletons Andrii Nakryiko
2024-02-16  2:40   ` Kui-Feng Lee
2024-02-16 16:54     ` Andrii Nakryiko

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.