Skip to contentSkip to conversation

Allow storage of flexible metadata information for eBPF programs

PATCH In review YiFei Zhu 7 participants 24 messages Aug 20, 2020 – Sep 3, 2020

Subsystems (10)
lore
Share & tools
lore

YiFei Zhu

author
#0 lore raw

cover Allow storage of flexible metadata information for eBPF programs

From: YiFei Zhu <[email protected]>

Currently, if a user wants to store arbitrary metadata for an eBPF program, for example, the program build commit hash or version, they could store it in a map, and conveniently libbpf uses .data section to populate an internal map. However, if the program does not actually reference the map, then the map would be de-refcounted and freed.

This patch set introduces a new syscall BPF_PROG_BIND_MAP to add a map to a program's used_maps, even if the program instructions does not reference the map. libbpf is extended to recognize the .metadata section and load it as an internal map, and use the new syscall to ensure the map is bound. bpftool is also extended to have a new flag to prog subcommand, "--metadata" to dump the contents of the metadata section without a separate map dump call.

An example use of this would be BPF C file declaring:

  char commit_hash[] SEC(".metadata") = "abcdef123456";

and bpftool would emit:

  $ bpftool prog --metadata
  [...]
  	metadata:
  		commit_hash = "abcdef123456"

Patch 1 protects the used_maps array and count with a mutex.

Patch 2 implements the new syscall.

Patch 3 extends libbpf to have a wrapper around the syscall, probe the kernel for support of this new syscall, and use it on .metadata section if supported and the section exists.

Patch 4 extends bpftool so that it is able to dump metadata from prog show.

Patch 5 adds a test to check the metadata loading and dumping.

Changes since RFC:
* Fixed a few missing unlocks, and missing close while iterating map fds.
* Move mutex initialization to right after prog aux allocation, and mutex destroy to right after prog aux free.
* s/ADD_MAP/BIND_MAP/
* Use mutex only instead of RCU to protect the used_map array & count.

YiFei Zhu (5):
bpf: Mutex protect used_maps array and count
bpf: Add BPF_PROG_BIND_MAP syscall
libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section
bpftool: support dumping metadata
selftests/bpf: Test bpftool loading and dumping metadata

 .../net/ethernet/netronome/nfp/bpf/offload.c  |  18 ++-
 include/linux/bpf.h                           |   1 +
 include/uapi/linux/bpf.h                      |   7 +
 kernel/bpf/core.c                             |  15 +-
 kernel/bpf/syscall.c                          |  81 ++++++++++-
 net/core/dev.c                                |  11 +-
 tools/bpf/bpftool/json_writer.c               |   6 +
 tools/bpf/bpftool/json_writer.h               |   3 +
 tools/bpf/bpftool/main.c                      |  10 ++
 tools/bpf/bpftool/main.h                      |   1 +
 tools/bpf/bpftool/prog.c                      | 135 ++++++++++++++++++
 tools/include/uapi/linux/bpf.h                |   7 +
 tools/lib/bpf/bpf.c                           |  11 ++
 tools/lib/bpf/bpf.h                           |   1 +
 tools/lib/bpf/libbpf.c                        | 100 ++++++++++++-
 tools/lib/bpf/libbpf.map                      |   1 +
 tools/testing/selftests/bpf/Makefile          |   3 +-
 .../selftests/bpf/progs/metadata_unused.c     |  15 ++
 .../selftests/bpf/progs/metadata_used.c       |  15 ++
 .../selftests/bpf/test_bpftool_metadata.sh    |  82 +++++++++++
 20 files changed, 504 insertions(+), 19 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/metadata_unused.c
 create mode 100644 tools/testing/selftests/bpf/progs/metadata_used.c
 create mode 100755 tools/testing/selftests/bpf/test_bpftool_metadata.sh
Signature · 1 line
2.28.0
author
#1 lore raw

PATCH 1/5 bpf: Mutex protect used_maps array and count

From: YiFei Zhu <[email protected]>

To support modifying the used_maps array, we use a mutex to protect the use of the counter and the array. The mutex is initialized right after the prog aux is allocated, and destroyed right before prog aux is freed. This way we guarantee it's initialized for both cBPF and eBPF.

---
 .../net/ethernet/netronome/nfp/bpf/offload.c   | 18 ++++++++++++------
 include/linux/bpf.h                            |  1 +
 kernel/bpf/core.c                              | 15 +++++++++++----
 kernel/bpf/syscall.c                           | 16 ++++++++++++----
 net/core/dev.c                                 | 11 ++++++++---
 5 files changed, 44 insertions(+), 17 deletions(-)
drivers/net/ethernet/netronome/nfp/bpf/offload.c +12 -6
index ac02369174a9..53851853562c 100644
@@ -111,7 +111,9 @@ static int
 nfp_map_ptrs_record(struct nfp_app_bpf *bpf, struct nfp_prog *nfp_prog,
 		    struct bpf_prog *prog)
 {
-	int i, cnt, err;
+	int i, cnt, err = 0;
+
+	mutex_lock(&prog->aux->used_maps_mutex);
 
 	/* Quickly count the maps we will have to remember */
 	cnt = 0;
@@ -119,13 +121,15 @@ nfp_map_ptrs_record(struct nfp_app_bpf *bpf, struct nfp_prog *nfp_prog,
 		if (bpf_map_offload_neutral(prog->aux->used_maps[i]))
 			cnt++;
 	if (!cnt)
-		return 0;
+		goto out;
 
 	nfp_prog->map_records = kmalloc_array(cnt,
 					      sizeof(nfp_prog->map_records[0]),
 					      GFP_KERNEL);
-	if (!nfp_prog->map_records)
-		return -ENOMEM;
+	if (!nfp_prog->map_records) {
+		err = -ENOMEM;
+		goto out;
+	}
 
 	for (i = 0; i < prog->aux->used_map_cnt; i++)
 		if (bpf_map_offload_neutral(prog->aux->used_maps[i])) {
@@ -133,12 +137,14 @@ nfp_map_ptrs_record(struct nfp_app_bpf *bpf, struct nfp_prog *nfp_prog,
 						 prog->aux->used_maps[i]);
 			if (err) {
 				nfp_map_ptrs_forget(bpf, nfp_prog);
-				return err;
+				goto out;
 			}
 		}
 	WARN_ON(cnt != nfp_prog->map_records_cnt);
 
-	return 0;
+out:
+	mutex_unlock(&prog->aux->used_maps_mutex);
+	return err;
 }
 
 static int
include/linux/bpf.h +1 -0
index 55f694b63164..abbdde104cea 100644
@@ -723,6 +723,7 @@ struct bpf_prog_aux {
 	struct bpf_ksym ksym;
 	const struct bpf_prog_ops *ops;
 	struct bpf_map **used_maps;
+	struct mutex used_maps_mutex; /* mutex for used_maps and used_map_cnt */
 	struct bpf_prog *prog;
 	struct user_struct *user;
 	u64 load_time; /* ns since boottime */
kernel/bpf/core.c +11 -4
index ed0b3578867c..2a20c2833996 100644
@@ -98,6 +98,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
 	fp->jit_requested = ebpf_jit_enabled();
 
 	INIT_LIST_HEAD_RCU(&fp->aux->ksym.lnode);
+	mutex_init(&fp->aux->used_maps_mutex);
 
 	return fp;
 }
@@ -253,6 +254,7 @@ struct bpf_prog *bpf_prog_realloc(struct bpf_prog *fp_old, unsigned int size,
 void __bpf_prog_free(struct bpf_prog *fp)
 {
 	if (fp->aux) {
+		mutex_destroy(&fp->aux->used_maps_mutex);
 		free_percpu(fp->aux->stats);
 		kfree(fp->aux->poke_tab);
 		kfree(fp->aux);
@@ -1747,8 +1749,9 @@ bool bpf_prog_array_compatible(struct bpf_array *array,
 static int bpf_check_tail_call(const struct bpf_prog *fp)
 {
 	struct bpf_prog_aux *aux = fp->aux;
-	int i;
+	int i, ret = 0;
 
+	mutex_lock(&aux->used_maps_mutex);
 	for (i = 0; i < aux->used_map_cnt; i++) {
 		struct bpf_map *map = aux->used_maps[i];
 		struct bpf_array *array;
@@ -1757,11 +1760,15 @@ static int bpf_check_tail_call(const struct bpf_prog *fp)
 			continue;
 
 		array = container_of(map, struct bpf_array, map);
-		if (!bpf_prog_array_compatible(array, fp))
-			return -EINVAL;
+		if (!bpf_prog_array_compatible(array, fp)) {
+			ret = -EINVAL;
+			goto out;
+		}
 	}
 
-	return 0;
+out:
+	mutex_unlock(&aux->used_maps_mutex);
+	return ret;
 }
 
 static void bpf_prog_select_func(struct bpf_prog *fp)
kernel/bpf/syscall.c +12 -4
index 86299a292214..f49fd709ccd5 100644
@@ -3152,21 +3152,25 @@ static const struct bpf_map *bpf_map_from_imm(const struct bpf_prog *prog,
 	const struct bpf_map *map;
 	int i;
 
+	mutex_lock(&prog->aux->used_maps_mutex);
 	for (i = 0, *off = 0; i < prog->aux->used_map_cnt; i++) {
 		map = prog->aux->used_maps[i];
 		if (map == (void *)addr) {
 			*type = BPF_PSEUDO_MAP_FD;
-			return map;
+			goto out;
 		}
 		if (!map->ops->map_direct_value_meta)
 			continue;
 		if (!map->ops->map_direct_value_meta(map, addr, off)) {
 			*type = BPF_PSEUDO_MAP_VALUE;
-			return map;
+			goto out;
 		}
 	}
+	map = NULL;
 
-	return NULL;
+out:
+	mutex_unlock(&prog->aux->used_maps_mutex);
+	return map;
 }
 
 static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog,
@@ -3284,6 +3288,7 @@ static int bpf_prog_get_info_by_fd(struct file *file,
 	memcpy(info.tag, prog->tag, sizeof(prog->tag));
 	memcpy(info.name, prog->aux->name, sizeof(prog->aux->name));
 
+	mutex_lock(&prog->aux->used_maps_mutex);
 	ulen = info.nr_map_ids;
 	info.nr_map_ids = prog->aux->used_map_cnt;
 	ulen = min_t(u32, info.nr_map_ids, ulen);
@@ -3293,9 +3298,12 @@ static int bpf_prog_get_info_by_fd(struct file *file,
 
 		for (i = 0; i < ulen; i++)
 			if (put_user(prog->aux->used_maps[i]->id,
-				     &user_map_ids[i]))
+				     &user_map_ids[i])) {
+				mutex_unlock(&prog->aux->used_maps_mutex);
 				return -EFAULT;
+			}
 	}
+	mutex_unlock(&prog->aux->used_maps_mutex);
 
 	err = set_info_rec_size(&info);
 	if (err)
net/core/dev.c +8 -3
index b5d1129d8310..6957b31127d9 100644
@@ -5441,15 +5441,20 @@ static int generic_xdp_install(struct net_device *dev, struct netdev_bpf *xdp)
 	if (new) {
 		u32 i;
 
+		mutex_lock(&new->aux->used_maps_mutex);
+
 		/* generic XDP does not work with DEVMAPs that can
 		 * have a bpf_prog installed on an entry
 		 */
 		for (i = 0; i < new->aux->used_map_cnt; i++) {
-			if (dev_map_can_have_prog(new->aux->used_maps[i]))
-				return -EINVAL;
-			if (cpu_map_prog_allowed(new->aux->used_maps[i]))
+			if (dev_map_can_have_prog(new->aux->used_maps[i]) ||
+			    cpu_map_prog_allowed(new->aux->used_maps[i])) {
+				mutex_unlock(&new->aux->used_maps_mutex);
 				return -EINVAL;
+			}
 		}
+
+		mutex_unlock(&new->aux->used_maps_mutex);
 	}
 
 	switch (xdp->command) {
Signature · 1 line
2.28.0
Acked-by on v1 1/5
#2 lore raw

Re: [PATCH bpf-next 1/5] bpf: Mutex protect used_maps array and count

Quoted 9 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

To support modifying the used_maps array, we use a mutex to protect the use of the counter and the array. The mutex is initialized right after the prog aux is allocated, and destroyed right before prog aux is freed. This way we guarantee it's initialized for both cBPF and eBPF.

author
#3 lore raw

PATCH 2/5 bpf: Add BPF_PROG_BIND_MAP syscall

In reply to YiFei Zhu

From: YiFei Zhu <[email protected]>

This syscall binds a map to a program. -EEXIST if the map is already bound to the program.

---
 include/uapi/linux/bpf.h       |  7 ++++
 kernel/bpf/syscall.c           | 65 ++++++++++++++++++++++++++++++++++
 tools/include/uapi/linux/bpf.h |  7 ++++
 3 files changed, 79 insertions(+)
include/uapi/linux/bpf.h +7 -0
index 0480f893facd..d11b2ee62148 100644
@@ -124,6 +124,7 @@ enum bpf_cmd {
 	BPF_ENABLE_STATS,
 	BPF_ITER_CREATE,
 	BPF_LINK_DETACH,
+	BPF_PROG_BIND_MAP,
 };
 
 enum bpf_map_type {
@@ -649,6 +650,12 @@ union bpf_attr {
 		__u32		flags;
 	} iter_create;
 
+	struct { /* struct used by BPF_PROG_BIND_MAP command */
+		__u32		prog_fd;
+		__u32		map_fd;
+		__u32		flags;		/* extra flags */
+	} prog_bind_map;
+
 } __attribute__((aligned(8)));
 
 /* The description below is an attempt at providing documentation to eBPF
kernel/bpf/syscall.c +65 -0

77 lines not on this page · raw message

tools/include/uapi/linux/bpf.h +7 -0
index 0480f893facd..d11b2ee62148 100644
@@ -124,6 +124,7 @@ enum bpf_cmd {
 	BPF_ENABLE_STATS,
 	BPF_ITER_CREATE,
 	BPF_LINK_DETACH,
+	BPF_PROG_BIND_MAP,
 };
 
 enum bpf_map_type {
@@ -649,6 +650,12 @@ union bpf_attr {
 		__u32		flags;
 	} iter_create;
 
+	struct { /* struct used by BPF_PROG_BIND_MAP command */
+		__u32		prog_fd;
+		__u32		map_fd;
+		__u32		flags;		/* extra flags */
+	} prog_bind_map;
+
 } __attribute__((aligned(8)));
 
 /* The description below is an attempt at providing documentation to eBPF
Signature · 1 line
2.28.0
author
#5 lore raw

PATCH 3/5 libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

In reply to YiFei Zhu

From: YiFei Zhu <[email protected]>

The patch adds a simple wrapper bpf_prog_bind_map around the syscall. And when using libbpf to load a program, it will probe the kernel for the support of this syscall, and scan for the .metadata ELF section and load it as an internal map like a .data section.

In the case that kernel supports the BPF_PROG_BIND_MAP syscall and a .metadata section exists, the map will be explicitly bound to the program via the syscall immediately after program is loaded. -EEXIST is ignored for this syscall.

---
 tools/lib/bpf/bpf.c      |  11 +++++
 tools/lib/bpf/bpf.h      |   1 +
 tools/lib/bpf/libbpf.c   | 100 ++++++++++++++++++++++++++++++++++++++-
 tools/lib/bpf/libbpf.map |   1 +
 4 files changed, 112 insertions(+), 1 deletion(-)
tools/lib/bpf/bpf.c +11 -0
index 82b983ff6569..383b29ecb1fd 100644
@@ -872,3 +872,14 @@ int bpf_enable_stats(enum bpf_stats_type type)
 
 	return sys_bpf(BPF_ENABLE_STATS, &attr, sizeof(attr));
 }
+
+int bpf_prog_bind_map(int prog_fd, int map_fd, int flags)
+{
+	union bpf_attr attr = {};
+
+	attr.prog_bind_map.prog_fd = prog_fd;
+	attr.prog_bind_map.map_fd = map_fd;
+	attr.prog_bind_map.flags = flags;
+
+	return sys_bpf(BPF_PROG_BIND_MAP, &attr, sizeof(attr));
+}
tools/lib/bpf/bpf.h +1 -0
index 015d13f25fcc..32994a4e0bf6 100644
@@ -243,6 +243,7 @@ LIBBPF_API int bpf_task_fd_query(int pid, int fd, __u32 flags, char *buf,
 enum bpf_stats_type; /* defined in up-to-date linux/bpf.h */
 LIBBPF_API int bpf_enable_stats(enum bpf_stats_type type);
 
+LIBBPF_API int bpf_prog_bind_map(int prog_fd, int map_fd, int flags);
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
tools/lib/bpf/libbpf.c +99 -1

196 lines not on this page · raw message

tools/lib/bpf/libbpf.map +1 -0
index e35bd6cdbdbf..4baf18a6df69 100644
@@ -288,6 +288,7 @@ LIBBPF_0.1.0 {
 		bpf_map__set_value_size;
 		bpf_map__type;
 		bpf_map__value_size;
+		bpf_prog_bind_map;
 		bpf_program__attach_xdp;
 		bpf_program__autoload;
 		bpf_program__is_sk_lookup;
Signature · 1 line
2.28.0
#6 lore raw

Re: [PATCH bpf-next 3/5] libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

Quoted 44 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

The patch adds a simple wrapper bpf_prog_bind_map around the syscall. And when using libbpf to load a program, it will probe the kernel for the support of this syscall, and scan for the .metadata ELF section and load it as an internal map like a .data section.

In the case that kernel supports the BPF_PROG_BIND_MAP syscall and a .metadata section exists, the map will be explicitly bound to the program via the syscall immediately after program is loaded. -EEXIST is ignored for this syscall.

---
  tools/lib/bpf/bpf.c      |  11 +++++
  tools/lib/bpf/bpf.h      |   1 +
  tools/lib/bpf/libbpf.c   | 100 ++++++++++++++++++++++++++++++++++++++-
  tools/lib/bpf/libbpf.map |   1 +
  4 files changed, 112 insertions(+), 1 deletion(-)
tools/lib/bpf/bpf.c +11 -0
index 82b983ff6569..383b29ecb1fd 100644
@@ -872,3 +872,14 @@ int bpf_enable_stats(enum bpf_stats_type type)
 
  	return sys_bpf(BPF_ENABLE_STATS, &attr, sizeof(attr));
  }
+
+int bpf_prog_bind_map(int prog_fd, int map_fd, int flags)
+{
+	union bpf_attr attr = {};
+
+	attr.prog_bind_map.prog_fd = prog_fd;
+	attr.prog_bind_map.map_fd = map_fd;
+	attr.prog_bind_map.flags = flags;
+
+	return sys_bpf(BPF_PROG_BIND_MAP, &attr, sizeof(attr));
+}
tools/lib/bpf/bpf.h +0 -0
index 015d13f25fcc..32994a4e0bf6 100644

tools/lib/bpf/bpf.h LIBBPF_API int bpf_task_fd_query(int pid, int fd, __u32 flags, char *buf, · line 246

Quoted 4 lines
tools/lib/bpf/bpf.h +1 -0
@@ -243,6 +243,7 @@ LIBBPF_API int bpf_task_fd_query(int pid, int fd, __u32 flags, char *buf,
  enum bpf_stats_type; /* defined in up-to-date linux/bpf.h */
  LIBBPF_API int bpf_enable_stats(enum bpf_stats_type type);
 
+LIBBPF_API int bpf_prog_bind_map(int prog_fd, int map_fd, int flags);

Maybe put "flags" as an optional parameter? Currently "flags" is not used. Not sure how widely it may be used in the future. See other syscall interface in the same file, e.g., bpf_link_create().

Quoted 166 lines
tools/lib/bpf/bpf.h +0 -0
  #ifdef __cplusplus
  } /* extern "C" */
  #endif
tools/lib/bpf/libbpf.c +63 -1
index 77d420c02094..4725859099c5 100644
@@ -174,6 +174,8 @@ enum kern_feature_id {
  	FEAT_EXP_ATTACH_TYPE,
  	/* bpf_probe_read_{kernel,user}[_str] helpers */
  	FEAT_PROBE_READ_KERN,
+	/* bpf_prog_bind_map helper */
+	FEAT_PROG_BIND_MAP,
  	__FEAT_CNT,
  };
 
@@ -283,6 +285,7 @@ struct bpf_struct_ops {
  #define KCONFIG_SEC ".kconfig"
  #define KSYMS_SEC ".ksyms"
  #define STRUCT_OPS_SEC ".struct_ops"
+#define METADATA_SEC ".metadata"
 
  enum libbpf_map_type {
  	LIBBPF_MAP_UNSPEC,
@@ -290,6 +293,7 @@ enum libbpf_map_type {
  	LIBBPF_MAP_BSS,
  	LIBBPF_MAP_RODATA,
  	LIBBPF_MAP_KCONFIG,
+	LIBBPF_MAP_METADATA,
  };
 
  static const char * const libbpf_type_to_btf_name[] = {
@@ -297,6 +301,7 @@ static const char * const libbpf_type_to_btf_name[] = {
  	[LIBBPF_MAP_BSS]	= BSS_SEC,
  	[LIBBPF_MAP_RODATA]	= RODATA_SEC,
  	[LIBBPF_MAP_KCONFIG]	= KCONFIG_SEC,
+	[LIBBPF_MAP_METADATA]	= METADATA_SEC,
  };
 
  struct bpf_map {
@@ -375,6 +380,8 @@ struct bpf_object {
  	size_t nr_maps;
  	size_t maps_cap;
 
+	struct bpf_map *metadata_map;
+
  	char *kconfig;
  	struct extern_desc *externs;
  	int nr_extern;
@@ -398,6 +405,7 @@ struct bpf_object {
  		Elf_Data *rodata;
  		Elf_Data *bss;
  		Elf_Data *st_ops_data;
+		Elf_Data *metadata;
  		size_t strtabidx;
  		struct {
  			GElf_Shdr shdr;
@@ -413,6 +421,7 @@ struct bpf_object {
  		int rodata_shndx;
  		int bss_shndx;
  		int st_ops_shndx;
+		int metadata_shndx;
  	} efile;
  	/*
  	 * All loaded bpf_object is linked in a list, which is
@@ -1022,6 +1031,7 @@ static struct bpf_object *bpf_object__new(const char *path,
  	obj->efile.obj_buf_sz = obj_buf_sz;
  	obj->efile.maps_shndx = -1;
  	obj->efile.btf_maps_shndx = -1;
+	obj->efile.metadata_shndx = -1;
  	obj->efile.data_shndx = -1;
  	obj->efile.rodata_shndx = -1;
  	obj->efile.bss_shndx = -1;
@@ -1387,6 +1397,9 @@ bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type,
  	if (data)
  		memcpy(map->mmaped, data, data_sz);
 
+	if (type == LIBBPF_MAP_METADATA)
+		obj->metadata_map = map;
+
  	pr_debug("map %td is \"%s\"\n", map - obj->maps, map->name);
  	return 0;
  }
@@ -1422,6 +1435,14 @@ static int bpf_object__init_global_data_maps(struct bpf_object *obj)
  		if (err)
  			return err;
  	}
+	if (obj->efile.metadata_shndx >= 0) {
+		err = bpf_object__init_internal_map(obj, LIBBPF_MAP_METADATA,
+						    obj->efile.metadata_shndx,
+						    obj->efile.metadata->d_buf,
+						    obj->efile.metadata->d_size);
+		if (err)
+			return err;
+	}
  	return 0;
  }
 
@@ -2698,6 +2719,9 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
  			} else if (strcmp(name, STRUCT_OPS_SEC) == 0) {
  				obj->efile.st_ops_data = data;
  				obj->efile.st_ops_shndx = idx;
+			} else if (strcmp(name, METADATA_SEC) == 0) {
+				obj->efile.metadata = data;
+				obj->efile.metadata_shndx = idx;
  			} else {
  				pr_debug("skip section(%d) %s\n", idx, name);
  			}
@@ -3111,7 +3135,8 @@ static bool bpf_object__shndx_is_data(const struct bpf_object *obj,
  {
  	return shndx == obj->efile.data_shndx ||
  	       shndx == obj->efile.bss_shndx ||
-	       shndx == obj->efile.rodata_shndx;
+	       shndx == obj->efile.rodata_shndx ||
+	       shndx == obj->efile.metadata_shndx;
  }
 
  static bool bpf_object__shndx_is_maps(const struct bpf_object *obj,
@@ -3132,6 +3157,8 @@ bpf_object__section_to_libbpf_map_type(const struct bpf_object *obj, int shndx)
  		return LIBBPF_MAP_RODATA;
  	else if (shndx == obj->efile.symbols_shndx)
  		return LIBBPF_MAP_KCONFIG;
+	else if (shndx == obj->efile.metadata_shndx)
+		return LIBBPF_MAP_METADATA;
  	else
  		return LIBBPF_MAP_UNSPEC;
  }
@@ -3655,6 +3682,60 @@ static int probe_kern_probe_read_kernel(void)
  	return probe_fd(bpf_load_program_xattr(&attr, NULL, 0));
  }
 
+static int probe_prog_bind_map(void)
+{
+	struct bpf_load_program_attr prog_attr;
+	struct bpf_create_map_attr map_attr;
+	char *cp, errmsg[STRERR_BUFSIZE];
+	struct bpf_insn insns[] = {
+		BPF_MOV64_IMM(BPF_REG_0, 0),
+		BPF_EXIT_INSN(),
+	};
+	int ret = 0, prog, map;
+
+	if (!kernel_supports(FEAT_GLOBAL_DATA))
+		return 0;
+
+	memset(&map_attr, 0, sizeof(map_attr));
+	map_attr.map_type = BPF_MAP_TYPE_ARRAY;
+	map_attr.key_size = sizeof(int);
+	map_attr.value_size = 32;
+	map_attr.max_entries = 1;
+
+	map = bpf_create_map_xattr(&map_attr);
+	if (map < 0) {
+		ret = -errno;
+		cp = libbpf_strerror_r(ret, errmsg, sizeof(errmsg));
+		pr_warn("Error in %s():%s(%d). Couldn't create simple array map.\n",
+			__func__, cp, -ret);
+		return ret;
+	}
+
+	memset(&prog_attr, 0, sizeof(prog_attr));
+	prog_attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
+	prog_attr.insns = insns;
+	prog_attr.insns_cnt = ARRAY_SIZE(insns);
+	prog_attr.license = "GPL";
+

tools/lib/bpf/libbpf.c static int probe_kern_probe_read_kernel(void) · line 3729

Quoted 10 lines
tools/lib/bpf/libbpf.c +10 -0
+	prog = bpf_load_program_xattr(&prog_attr, NULL, 0);
+	if (prog < 0) {
+		ret = -errno;
+		cp = libbpf_strerror_r(ret, errmsg, sizeof(errmsg));
+		pr_warn("Error in %s():%s(%d). Couldn't create simple program.\n",
+			__func__, cp, -ret);
+
+		close(map);
+		return ret;
+	}

A lot of duplicated codes here vs. probe_global_data. Can we abstract common codes into separate routines?

Quoted 23 lines
tools/lib/bpf/libbpf.c +12 -0
+
+	if (!bpf_prog_bind_map(prog, map, 0))
+		ret = 1;
+
+	close(map);
+	close(prog);
+	return ret;
+}
+
  enum kern_feature_result {
  	FEAT_UNKNOWN = 0,
  	FEAT_SUPPORTED = 1,
@@ -3695,6 +3776,9 @@ static struct kern_feature_desc {
  	},
  	[FEAT_PROBE_READ_KERN] = {
  		"bpf_probe_read_kernel() helper", probe_kern_probe_read_kernel,
+	},
+	[FEAT_PROG_BIND_MAP] = {
+		"bpf_prog_bind_map() helper", probe_prog_bind_map,
  	}
  };
 

tools/lib/bpf/libbpf.c load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt, · line 6044

Quoted 7 lines
tools/lib/bpf/libbpf.c +4 -0
@@ -5954,6 +6038,20 @@ load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt,
  	if (ret >= 0) {
  		if (log_buf && load_attr.log_level)
  			pr_debug("verifier log:\n%s", log_buf);
+
+		if (prog->obj->metadata_map && kernel_supports(FEAT_PROG_BIND_MAP)) {
+			if (bpf_prog_bind_map(ret, bpf_map__fd(prog->obj->metadata_map), 0) &&
+			    errno != EEXIST) {

could you explain and possibly add comments in the code why EEXIST is ignored in the failure case?

tools/lib/bpf/libbpf.c load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt, · line 6047

Quoted 3 lines
tools/lib/bpf/libbpf.c +3 -0
+				int fd = ret;
+
+				ret = -errno;

libbpf_strerror_r understands positive and negative errno, so no need "ret = -errno".

Question: should bpftool freeze the metadata map or not?

Quoted 15 lines
tools/lib/bpf/libbpf.c +7 -0
+				cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg));
+				pr_warn("add metadata map failed: %s\n", cp);
+				close(fd);
+				goto out;
+			}
+		}
+
  		*pfd = ret;
  		ret = 0;
  		goto out;
tools/lib/bpf/libbpf.map +0 -0
index e35bd6cdbdbf..4baf18a6df69 100644

tools/lib/bpf/libbpf.map LIBBPF_0.1.0 { · line 290

Quoted 3 lines
tools/lib/bpf/libbpf.map +0 -0
@@ -288,6 +288,7 @@ LIBBPF_0.1.0 {
  		bpf_map__set_value_size;
  		bpf_map__type;
  		bpf_map__value_size;

This needs to be in a new kernel release. For example
LIBBPF_0.1.1

Quoted 5 lines
tools/lib/bpf/libbpf.map +1 -0
+		bpf_prog_bind_map;
  		bpf_program__attach_xdp;
  		bpf_program__autoload;
  		bpf_program__is_sk_lookup;
#7 lore raw

Re: [PATCH bpf-next 3/5] libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

Quoted 7 lines · Yonghong Song

tools/lib/bpf/libbpf.c load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt, · line 6047

Quoted 4 lines
tools/lib/bpf/libbpf.c +3 -0
+                             int fd = ret;
+
+                             ret = -errno;

libbpf_strerror_r understands positive and negative errno, so no need "ret = -errno".

I don't understand this one. The use of ret = -errno here is that when we goto out later we return a -errno. If this line is removed then fd is returned after fd is closed, in the case of a bind map failure, without writing to *pfd. Am I misunderstanding something?

YiFei Zhu

#8 lore raw

Re: [PATCH bpf-next 3/5] libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

Quoted 6 lines · YiFei Zhu
Quoted 5 lines · Yonghong Song
Quoted 4 lines
tools/lib/bpf/libbpf.c +3 -0
+                             int fd = ret;
+
+                             ret = -errno;
Quoted 7 lines
Quoted 2 lines

libbpf_strerror_r understands positive and negative errno, so no need "ret = -errno".

I don't understand this one. The use of ret = -errno here is that when we goto out later we return a -errno. If this line is removed then fd is returned after fd is closed, in the case of a bind map failure, without writing to *pfd. Am I misunderstanding something?

Aha, sorry. My bad. I missed that 'ret' is used later for the negative code return value. Yes, your code looks fine.

Quoted 3 lines

YiFei Zhu

#9 lore raw

Re: [PATCH bpf-next 3/5] libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

In reply to YiFei Zhu

YiFei Zhu <[email protected]> [Thu, 2020-08-20 02:43 -0700]:

Quoted 13 lines

From: YiFei Zhu <[email protected]>

The patch adds a simple wrapper bpf_prog_bind_map around the syscall. And when using libbpf to load a program, it will probe the kernel for the support of this syscall, and scan for the .metadata ELF section and load it as an internal map like a .data section.

In the case that kernel supports the BPF_PROG_BIND_MAP syscall and a .metadata section exists, the map will be explicitly bound to the program via the syscall immediately after program is loaded. -EEXIST is ignored for this syscall.

...

tools/lib/bpf/libbpf.c bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type, · line 1401

Quoted 6 lines
tools/lib/bpf/libbpf.c +2 -0
@@ -1387,6 +1397,9 @@ bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type,
 	if (data)
 		memcpy(map->mmaped, data, data_sz);
 
+	if (type == LIBBPF_MAP_METADATA)
+		obj->metadata_map = map;

I wonder if the map should have BPF_F_RDONLY / BPF_F_RDONLY_PROG flags set by libbpf?

At least in my use-case metadata should never change once the map is created, neither from program nor from syscall side.

Quoted 4 lines
tools/lib/bpf/libbpf.c +1 -0
+
 	pr_debug("map %td is \"%s\"\n", map - obj->maps, map->name);
 	return 0;
 }
Signature · 1 line
Andrey Ignatov
#10 lore raw

Re: [PATCH bpf-next 3/5] libbpf: Add BPF_PROG_BIND_MAP syscall and use it on .metadata section

In reply to YiFei Zhu
Quoted 26 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

The patch adds a simple wrapper bpf_prog_bind_map around the syscall. And when using libbpf to load a program, it will probe the kernel for the support of this syscall, and scan for the .metadata ELF section and load it as an internal map like a .data section.

In the case that kernel supports the BPF_PROG_BIND_MAP syscall and a .metadata section exists, the map will be explicitly bound to the program via the syscall immediately after program is loaded. -EEXIST is ignored for this syscall.

---
 tools/lib/bpf/bpf.c      |  11 +++++
 tools/lib/bpf/bpf.h      |   1 +
 tools/lib/bpf/libbpf.c   | 100 ++++++++++++++++++++++++++++++++++++++-
 tools/lib/bpf/libbpf.map |   1 +
 4 files changed, 112 insertions(+), 1 deletion(-)
tools/lib/bpf/bpf.c +0 -0
index 82b983ff6569..383b29ecb1fd 100644

tools/lib/bpf/bpf.c int bpf_enable_stats(enum bpf_stats_type type) · line 878

Quoted 7 lines
tools/lib/bpf/bpf.c +4 -0
@@ -872,3 +872,14 @@ int bpf_enable_stats(enum bpf_stats_type type)
 
        return sys_bpf(BPF_ENABLE_STATS, &attr, sizeof(attr));
 }
+
+int bpf_prog_bind_map(int prog_fd, int map_fd, int flags)
+{
+       union bpf_attr attr = {};

use explicit memset() to avoid potential issues with uninitialized paddings

Quoted 24 lines
tools/lib/bpf/bpf.c +7 -0
+
+       attr.prog_bind_map.prog_fd = prog_fd;
+       attr.prog_bind_map.map_fd = map_fd;
+       attr.prog_bind_map.flags = flags;
+
+       return sys_bpf(BPF_PROG_BIND_MAP, &attr, sizeof(attr));
+}
tools/lib/bpf/bpf.h +1 -0
index 015d13f25fcc..32994a4e0bf6 100644
@@ -243,6 +243,7 @@ LIBBPF_API int bpf_task_fd_query(int pid, int fd, __u32 flags, char *buf,
 enum bpf_stats_type; /* defined in up-to-date linux/bpf.h */
 LIBBPF_API int bpf_enable_stats(enum bpf_stats_type type);
 
+LIBBPF_API int bpf_prog_bind_map(int prog_fd, int map_fd, int flags);
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
tools/lib/bpf/libbpf.c +0 -0
index 77d420c02094..4725859099c5 100644

tools/lib/bpf/libbpf.c enum kern_feature_id { · line 177

Quoted 4 lines
tools/lib/bpf/libbpf.c +1 -0
@@ -174,6 +174,8 @@ enum kern_feature_id {
        FEAT_EXP_ATTACH_TYPE,
        /* bpf_probe_read_{kernel,user}[_str] helpers */
        FEAT_PROBE_READ_KERN,
+       /* bpf_prog_bind_map helper */

not helper, see below

Quoted 62 lines
tools/lib/bpf/libbpf.c +9 -0
+       FEAT_PROG_BIND_MAP,
        __FEAT_CNT,
 };
 
@@ -283,6 +285,7 @@ struct bpf_struct_ops {
 #define KCONFIG_SEC ".kconfig"
 #define KSYMS_SEC ".ksyms"
 #define STRUCT_OPS_SEC ".struct_ops"
+#define METADATA_SEC ".metadata"
 
 enum libbpf_map_type {
        LIBBPF_MAP_UNSPEC,
@@ -290,6 +293,7 @@ enum libbpf_map_type {
        LIBBPF_MAP_BSS,
        LIBBPF_MAP_RODATA,
        LIBBPF_MAP_KCONFIG,
+       LIBBPF_MAP_METADATA,
 };
 
 static const char * const libbpf_type_to_btf_name[] = {
@@ -297,6 +301,7 @@ static const char * const libbpf_type_to_btf_name[] = {
        [LIBBPF_MAP_BSS]        = BSS_SEC,
        [LIBBPF_MAP_RODATA]     = RODATA_SEC,
        [LIBBPF_MAP_KCONFIG]    = KCONFIG_SEC,
+       [LIBBPF_MAP_METADATA]   = METADATA_SEC,
 };
 
 struct bpf_map {
@@ -375,6 +380,8 @@ struct bpf_object {
        size_t nr_maps;
        size_t maps_cap;
 
+       struct bpf_map *metadata_map;
+
        char *kconfig;
        struct extern_desc *externs;
        int nr_extern;
@@ -398,6 +405,7 @@ struct bpf_object {
                Elf_Data *rodata;
                Elf_Data *bss;
                Elf_Data *st_ops_data;
+               Elf_Data *metadata;
                size_t strtabidx;
                struct {
                        GElf_Shdr shdr;
@@ -413,6 +421,7 @@ struct bpf_object {
                int rodata_shndx;
                int bss_shndx;
                int st_ops_shndx;
+               int metadata_shndx;
        } efile;
        /*
         * All loaded bpf_object is linked in a list, which is
@@ -1022,6 +1031,7 @@ static struct bpf_object *bpf_object__new(const char *path,
        obj->efile.obj_buf_sz = obj_buf_sz;
        obj->efile.maps_shndx = -1;
        obj->efile.btf_maps_shndx = -1;
+       obj->efile.metadata_shndx = -1;
        obj->efile.data_shndx = -1;
        obj->efile.rodata_shndx = -1;
        obj->efile.bss_shndx = -1;

tools/lib/bpf/libbpf.c bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type, · line 1401

Quoted 5 lines
tools/lib/bpf/libbpf.c +2 -0
@@ -1387,6 +1397,9 @@ bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type,
        if (data)
                memcpy(map->mmaped, data, data_sz);
 
+       if (type == LIBBPF_MAP_METADATA)
+               obj->metadata_map = map;

Let's keep the approach consistent with other special maps. See how it's done for Kconfig with kconfig_map_idx.

Quoted 68 lines
tools/lib/bpf/libbpf.c +32 -1
+
        pr_debug("map %td is \"%s\"\n", map - obj->maps, map->name);
        return 0;
 }
@@ -1422,6 +1435,14 @@ static int bpf_object__init_global_data_maps(struct bpf_object *obj)
                if (err)
                        return err;
        }
+       if (obj->efile.metadata_shndx >= 0) {
+               err = bpf_object__init_internal_map(obj, LIBBPF_MAP_METADATA,
+                                                   obj->efile.metadata_shndx,
+                                                   obj->efile.metadata->d_buf,
+                                                   obj->efile.metadata->d_size);
+               if (err)
+                       return err;
+       }
        return 0;
 }
 
@@ -2698,6 +2719,9 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
                        } else if (strcmp(name, STRUCT_OPS_SEC) == 0) {
                                obj->efile.st_ops_data = data;
                                obj->efile.st_ops_shndx = idx;
+                       } else if (strcmp(name, METADATA_SEC) == 0) {
+                               obj->efile.metadata = data;
+                               obj->efile.metadata_shndx = idx;
                        } else {
                                pr_debug("skip section(%d) %s\n", idx, name);
                        }
@@ -3111,7 +3135,8 @@ static bool bpf_object__shndx_is_data(const struct bpf_object *obj,
 {
        return shndx == obj->efile.data_shndx ||
               shndx == obj->efile.bss_shndx ||
-              shndx == obj->efile.rodata_shndx;
+              shndx == obj->efile.rodata_shndx ||
+              shndx == obj->efile.metadata_shndx;
 }
 
 static bool bpf_object__shndx_is_maps(const struct bpf_object *obj,
@@ -3132,6 +3157,8 @@ bpf_object__section_to_libbpf_map_type(const struct bpf_object *obj, int shndx)
                return LIBBPF_MAP_RODATA;
        else if (shndx == obj->efile.symbols_shndx)
                return LIBBPF_MAP_KCONFIG;
+       else if (shndx == obj->efile.metadata_shndx)
+               return LIBBPF_MAP_METADATA;
        else
                return LIBBPF_MAP_UNSPEC;
 }
@@ -3655,6 +3682,60 @@ static int probe_kern_probe_read_kernel(void)
        return probe_fd(bpf_load_program_xattr(&attr, NULL, 0));
 }
 
+static int probe_prog_bind_map(void)
+{
+       struct bpf_load_program_attr prog_attr;
+       struct bpf_create_map_attr map_attr;
+       char *cp, errmsg[STRERR_BUFSIZE];
+       struct bpf_insn insns[] = {
+               BPF_MOV64_IMM(BPF_REG_0, 0),
+               BPF_EXIT_INSN(),
+       };
+       int ret = 0, prog, map;
+
+       if (!kernel_supports(FEAT_GLOBAL_DATA))
+               return 0;
+
+       memset(&map_attr, 0, sizeof(map_attr));
+       map_attr.map_type = BPF_MAP_TYPE_ARRAY;

tools/lib/bpf/libbpf.c static int probe_kern_probe_read_kernel(void) · line 3710

Quoted 10 lines
tools/lib/bpf/libbpf.c +10 -0
+       map_attr.key_size = sizeof(int);
+       map_attr.value_size = 32;
+       map_attr.max_entries = 1;
+
+       map = bpf_create_map_xattr(&map_attr);
+       if (map < 0) {
+               ret = -errno;
+               cp = libbpf_strerror_r(ret, errmsg, sizeof(errmsg));
+               pr_warn("Error in %s():%s(%d). Couldn't create simple array map.\n",
+                       __func__, cp, -ret);

don't log here, it's already logged by few more basic feature checks

Quoted 6 lines
tools/lib/bpf/libbpf.c +6 -0
+               return ret;
+       }
+
+       memset(&prog_attr, 0, sizeof(prog_attr));
+       prog_attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
+       prog_attr.insns = insns;

tools/lib/bpf/libbpf.c static int probe_kern_probe_read_kernel(void) · line 3726

Quoted 10 lines
tools/lib/bpf/libbpf.c +10 -0
+       prog_attr.insns_cnt = ARRAY_SIZE(insns);
+       prog_attr.license = "GPL";
+
+       prog = bpf_load_program_xattr(&prog_attr, NULL, 0);
+       if (prog < 0) {
+               ret = -errno;
+               cp = libbpf_strerror_r(ret, errmsg, sizeof(errmsg));
+               pr_warn("Error in %s():%s(%d). Couldn't create simple program.\n",
+                       __func__, cp, -ret);
+

same, no need for logging

Quoted 16 lines
tools/lib/bpf/libbpf.c +12 -0
+               close(map);
+               return ret;
+       }
+
+       if (!bpf_prog_bind_map(prog, map, 0))
+               ret = 1;
+
+       close(map);
+       close(prog);
+       return ret;
+}
+
 enum kern_feature_result {
        FEAT_UNKNOWN = 0,
        FEAT_SUPPORTED = 1,

tools/lib/bpf/libbpf.c static struct kern_feature_desc { · line 3781

Quoted 6 lines
tools/lib/bpf/libbpf.c +3 -0
@@ -3695,6 +3776,9 @@ static struct kern_feature_desc {
        },
        [FEAT_PROBE_READ_KERN] = {
                "bpf_probe_read_kernel() helper", probe_kern_probe_read_kernel,
+       },
+       [FEAT_PROG_BIND_MAP] = {
+               "bpf_prog_bind_map() helper", probe_prog_bind_map,

it's not a helper, it's bpf() syscall command (BPF_PROG_BIND_MAP, right?), so "BPF_PROG_BIND_MAP support" would probably be an ok short description.

Quoted 6 lines
tools/lib/bpf/libbpf.c +0 -0
        }
 };
 
@@ -5954,6 +6038,20 @@ load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt,
        if (ret >= 0) {
                if (log_buf && load_attr.log_level)

tools/lib/bpf/libbpf.c load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt, · line 6049

Quoted 10 lines
tools/lib/bpf/libbpf.c +9 -0
                        pr_debug("verifier log:\n%s", log_buf);
+
+               if (prog->obj->metadata_map && kernel_supports(FEAT_PROG_BIND_MAP)) {
+                       if (bpf_prog_bind_map(ret, bpf_map__fd(prog->obj->metadata_map), 0) &&
+                           errno != EEXIST) {
+                               int fd = ret;
+
+                               ret = -errno;
+                               cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg));
+                               pr_warn("add metadata map failed: %s\n", cp);

please use error message consistent with most libbpf code, something like this should be enough:

pr_warn("prog '%s': failed to bind .metadata map: %d\n", prog->name, ret);

tools/lib/bpf/libbpf.c load_program(struct bpf_program *prog, struct bpf_insn *insns, int insns_cnt, · line 6050

Quoted 1 line
tools/lib/bpf/libbpf.c +1 -0
+                               close(fd);

Do we really want to fail loading the program because we failed to attach .metadata? Surely feature detection should ensure that this doesn't fail, but stil...

Quoted 22 lines
tools/lib/bpf/libbpf.c +4 -0
+                               goto out;
+                       }
+               }
+
                *pfd = ret;
                ret = 0;
                goto out;
tools/lib/bpf/libbpf.map +1 -1
index e35bd6cdbdbf..4baf18a6df69 100644
@@ -288,6 +288,7 @@ LIBBPF_0.1.0 {
                bpf_map__set_value_size;
                bpf_map__type;
                bpf_map__value_size;
+               bpf_prog_bind_map;
                bpf_program__attach_xdp;
                bpf_program__autoload;
                bpf_program__is_sk_lookup;
--

2.28.0

author
#11 lore raw

PATCH 4/5 bpftool: support dumping metadata

In reply to YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

  $ bpftool prog --metadata
  6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
  [...]
  	btf_id 4
  	metadata:
  		metadata_a = "foo"
  		metadata_b = 1
  $ bpftool prog --metadata --json --pretty
  [{
          "id": 6,
  [...]
          "btf_id": 4,
          "metadata": {
              "metadata_a": "foo",
              "metadata_b": 1
          }
      }
  ]
---
 tools/bpf/bpftool/json_writer.c |   6 ++
 tools/bpf/bpftool/json_writer.h |   3 +
 tools/bpf/bpftool/main.c        |  10 +++
 tools/bpf/bpftool/main.h        |   1 +
 tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
 5 files changed, 155 insertions(+)
tools/bpf/bpftool/json_writer.c +6 -0
index 86501cd3c763..7fea83bedf48 100644
@@ -119,6 +119,12 @@ void jsonw_pretty(json_writer_t *self, bool on)
 	self->pretty = on;
 }
 
+void jsonw_reset(json_writer_t *self)
+{
+	assert(self->depth == 0);
+	self->sep = '\0';
+}
+
 /* Basic blocks */
 static void jsonw_begin(json_writer_t *self, int c)
 {
tools/bpf/bpftool/json_writer.h +3 -0
index 35cf1f00f96c..8ace65cdb92f 100644
@@ -27,6 +27,9 @@ void jsonw_destroy(json_writer_t **self_p);
 /* Cause output to have pretty whitespace */
 void jsonw_pretty(json_writer_t *self, bool on);
 
+/* Reset separator to create new JSON */
+void jsonw_reset(json_writer_t *self);
+
 /* Add property name */
 void jsonw_name(json_writer_t *self, const char *name);
 
tools/bpf/bpftool/main.c +10 -0
index 4a191fcbeb82..a681d568cfa7 100644
@@ -28,6 +28,7 @@ bool show_pinned;
 bool block_mount;
 bool verifier_logs;
 bool relaxed_maps;
+bool dump_metadata;
 struct pinned_obj_table prog_table;
 struct pinned_obj_table map_table;
 struct pinned_obj_table link_table;
@@ -351,6 +352,10 @@ static int do_batch(int argc, char **argv)
 	return err;
 }
 
+enum bpftool_longonly_opts {
+	OPT_METADATA = 256,
+};
+
 int main(int argc, char **argv)
 {
 	static const struct option options[] = {
@@ -362,6 +367,7 @@ int main(int argc, char **argv)
 		{ "mapcompat",	no_argument,	NULL,	'm' },
 		{ "nomount",	no_argument,	NULL,	'n' },
 		{ "debug",	no_argument,	NULL,	'd' },
+		{ "metadata",	no_argument,	NULL,	OPT_METADATA },
 		{ 0 }
 	};
 	int opt, ret;
@@ -371,6 +377,7 @@ int main(int argc, char **argv)
 	json_output = false;
 	show_pinned = false;
 	block_mount = false;
+	dump_metadata = false;
 	bin_name = argv[0];
 
 	hash_init(prog_table.table);
@@ -412,6 +419,9 @@ int main(int argc, char **argv)
 			libbpf_set_print(print_all_levels);
 			verifier_logs = true;
 			break;
+		case OPT_METADATA:
+			dump_metadata = true;
+			break;
 		default:
 			p_err("unrecognized option '%s'", argv[optind - 1]);
 			if (json_output)
tools/bpf/bpftool/main.h +1 -0
index c46e52137b87..8750758e9150 100644
@@ -90,6 +90,7 @@ extern bool show_pids;
 extern bool block_mount;
 extern bool verifier_logs;
 extern bool relaxed_maps;
+extern bool dump_metadata;
 extern struct pinned_obj_table prog_table;
 extern struct pinned_obj_table map_table;
 extern struct pinned_obj_table link_table;
tools/bpf/bpftool/prog.c +135 -0

153 lines not on this page · raw message

Signature · 1 line
2.28.0
#12 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 146 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

   $ bpftool prog --metadata
   6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
   [...]
   	btf_id 4
   	metadata:
   		metadata_a = "foo"
   		metadata_b = 1
   $ bpftool prog --metadata --json --pretty
   [{
           "id": 6,
   [...]
           "btf_id": 4,
           "metadata": {
               "metadata_a": "foo",
               "metadata_b": 1
           }
       }
   ]
---
  tools/bpf/bpftool/json_writer.c |   6 ++
  tools/bpf/bpftool/json_writer.h |   3 +
  tools/bpf/bpftool/main.c        |  10 +++
  tools/bpf/bpftool/main.h        |   1 +
  tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
  5 files changed, 155 insertions(+)
tools/bpf/bpftool/json_writer.c +6 -0
index 86501cd3c763..7fea83bedf48 100644
@@ -119,6 +119,12 @@ void jsonw_pretty(json_writer_t *self, bool on)
  	self->pretty = on;
  }
 
+void jsonw_reset(json_writer_t *self)
+{
+	assert(self->depth == 0);
+	self->sep = '\0';
+}
+
  /* Basic blocks */
  static void jsonw_begin(json_writer_t *self, int c)
  {
tools/bpf/bpftool/json_writer.h +3 -0
index 35cf1f00f96c..8ace65cdb92f 100644
@@ -27,6 +27,9 @@ void jsonw_destroy(json_writer_t **self_p);
  /* Cause output to have pretty whitespace */
  void jsonw_pretty(json_writer_t *self, bool on);
 
+/* Reset separator to create new JSON */
+void jsonw_reset(json_writer_t *self);
+
  /* Add property name */
  void jsonw_name(json_writer_t *self, const char *name);
 
tools/bpf/bpftool/main.c +10 -0
index 4a191fcbeb82..a681d568cfa7 100644
@@ -28,6 +28,7 @@ bool show_pinned;
  bool block_mount;
  bool verifier_logs;
  bool relaxed_maps;
+bool dump_metadata;
  struct pinned_obj_table prog_table;
  struct pinned_obj_table map_table;
  struct pinned_obj_table link_table;
@@ -351,6 +352,10 @@ static int do_batch(int argc, char **argv)
  	return err;
  }
 
+enum bpftool_longonly_opts {
+	OPT_METADATA = 256,
+};
+
  int main(int argc, char **argv)
  {
  	static const struct option options[] = {
@@ -362,6 +367,7 @@ int main(int argc, char **argv)
  		{ "mapcompat",	no_argument,	NULL,	'm' },
  		{ "nomount",	no_argument,	NULL,	'n' },
  		{ "debug",	no_argument,	NULL,	'd' },
+		{ "metadata",	no_argument,	NULL,	OPT_METADATA },
  		{ 0 }
  	};
  	int opt, ret;
@@ -371,6 +377,7 @@ int main(int argc, char **argv)
  	json_output = false;
  	show_pinned = false;
  	block_mount = false;
+	dump_metadata = false;
  	bin_name = argv[0];
 
  	hash_init(prog_table.table);
@@ -412,6 +419,9 @@ int main(int argc, char **argv)
  			libbpf_set_print(print_all_levels);
  			verifier_logs = true;
  			break;
+		case OPT_METADATA:
+			dump_metadata = true;
+			break;
  		default:
  			p_err("unrecognized option '%s'", argv[optind - 1]);
  			if (json_output)
tools/bpf/bpftool/main.h +1 -0
index c46e52137b87..8750758e9150 100644
@@ -90,6 +90,7 @@ extern bool show_pids;
  extern bool block_mount;
  extern bool verifier_logs;
  extern bool relaxed_maps;
+extern bool dump_metadata;
  extern struct pinned_obj_table prog_table;
  extern struct pinned_obj_table map_table;
  extern struct pinned_obj_table link_table;
tools/bpf/bpftool/prog.c +5 -0
index d393eb8263a6..ee767b8d90fb 100644
@@ -151,6 +151,135 @@ static void show_prog_maps(int fd, __u32 num_maps)
  	}
  }
 
+static void show_prog_metadata(int fd, __u32 num_maps)
+{
+	struct bpf_prog_info prog_info = {};
+	struct bpf_map_info map_info = {};
+	__u32 prog_info_len = sizeof(prog_info);

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 168

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+	__u32 map_info_len = sizeof(map_info);
+	__u32 map_ids[num_maps];
+	void *value = NULL;
+	struct btf *btf = NULL;
+	const struct btf_type *t_datasec, *t_var;
+	struct btf_var_secinfo *vsi;
+	int key = 0;
+	unsigned int i, vlen;
+	int map_fd;
+	int err;

try to follow reverse christmas tree coding styple?

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 175

Quoted 7 lines
tools/bpf/bpftool/prog.c +7 -0
+
+	prog_info.nr_map_ids = num_maps;
+	prog_info.map_ids = ptr_to_u64(map_ids);
+
+	err = bpf_obj_get_info_by_fd(fd, &prog_info, &prog_info_len);
+	if (err || !prog_info.nr_map_ids)
+		return;

print out something for "err" case and "!prog_info.nr_map_ids" case? The same for some other below returns.

Quoted 26 lines
tools/bpf/bpftool/prog.c +26 -0
+
+	for (i = 0; i < prog_info.nr_map_ids; i++) {
+		map_fd = bpf_map_get_fd_by_id(map_ids[i]);
+		if (map_fd < 0)
+			return;
+
+		err = bpf_obj_get_info_by_fd(map_fd, &map_info, &map_info_len);
+		if (err)
+			goto out_close;
+
+		if (map_info.type != BPF_MAP_TYPE_ARRAY)
+			goto next_map;
+		if (map_info.key_size != sizeof(int))
+			goto next_map;
+		if (map_info.max_entries != 1)
+			goto next_map;
+		if (!map_info.btf_value_type_id)
+			goto next_map;
+		if (!strstr(map_info.name, ".metadata"))
+			goto next_map;
+
+		goto found;
+
+next_map:
+		close(map_fd);
+	}

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 211

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+
+	return;
+
+found:
+	value = malloc(map_info.value_size);
+	if (!value)
+		goto out_close;
+
+	if (bpf_map_lookup_elem(map_fd, &key, value))
+		goto out_free;

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

If we can put metadata in skeleton like
<metadata_type> *metadata;
and then it will be very easy for users to access it.

Quoted 17 lines
tools/bpf/bpftool/prog.c +17 -0
+
+	err = btf__get_from_id(map_info.btf_id, &btf);
+	if (err || !btf)
+		goto out_free;
+
+	t_datasec = btf__type_by_id(btf, map_info.btf_value_type_id);
+	if (BTF_INFO_KIND(t_datasec->info) != BTF_KIND_DATASEC)
+		goto out_free;
+
+	vlen = BTF_INFO_VLEN(t_datasec->info);
+	vsi = (struct btf_var_secinfo *)(t_datasec + 1);
+
+	if (json_output) {
+		struct btf_dumper d = {
+			.btf = btf,
+			.jw = json_wtr,
+			.is_plain_text = false,

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 238

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+		};
+
+		jsonw_name(json_wtr, "metadata");
+
+		jsonw_start_object(json_wtr);
+		for (i = 0; i < vlen; i++) {
+			t_var = btf__type_by_id(btf, vsi[i].type);
+
+			if (BTF_INFO_KIND(t_var->info) != BTF_KIND_VAR)
+				continue;

this should not happen.

Quoted 14 lines
tools/bpf/bpftool/prog.c +14 -0
+
+			jsonw_name(json_wtr, btf__name_by_offset(btf, t_var->name_off));
+			err = btf_dumper_type(&d, t_var->type, value + vsi[i].offset);
+			if (err)
+				break;
+		}
+		jsonw_end_object(json_wtr);
+	} else {
+		json_writer_t *btf_wtr = jsonw_new(stdout);
+		struct btf_dumper d = {
+			.btf = btf,
+			.jw = btf_wtr,
+			.is_plain_text = true,
+		};

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 262

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+		if (!btf_wtr)
+			goto out_free;
+
+		printf("\tmetadata:");
+
+		for (i = 0; i < vlen; i++) {
+			t_var = btf__type_by_id(btf, vsi[i].type);
+
+			if (BTF_INFO_KIND(t_var->info) != BTF_KIND_VAR)
+				continue;

this should not happen.

Quoted 44 lines
tools/bpf/bpftool/prog.c +26 -0
+
+			printf("\n\t\t%s = ", btf__name_by_offset(btf, t_var->name_off));
+
+			jsonw_reset(btf_wtr);
+			err = btf_dumper_type(&d, t_var->type, value + vsi[i].offset);
+			if (err)
+				break;
+		}
+
+		jsonw_destroy(&btf_wtr);
+	}
+
+out_free:
+	btf__free(btf);
+	free(value);
+
+out_close:
+	close(map_fd);
+}
+
  static void print_prog_header_json(struct bpf_prog_info *info)
  {
  	jsonw_uint_field(json_wtr, "id", info->id);
@@ -228,6 +357,9 @@ static void print_prog_json(struct bpf_prog_info *info, int fd)
 
  	emit_obj_refs_json(&refs_table, info->id, json_wtr);
 
+	if (dump_metadata)
+		show_prog_metadata(fd, info->nr_map_ids);
+
  	jsonw_end_object(json_wtr);
  }
 
@@ -297,6 +429,9 @@ static void print_prog_plain(struct bpf_prog_info *info, int fd)
  	emit_obj_refs_plain(&refs_table, info->id, "\n\tpids ");
 
  	printf("\n");
+
+	if (dump_metadata)
+		show_prog_metadata(fd, info->nr_map_ids);
  }
 
  static int show_prog(int fd)
#13 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 203 lines · Yonghong Song
Quoted 146 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

   $ bpftool prog --metadata
   6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
   [...]
   	btf_id 4
   	metadata:
   		metadata_a = "foo"
   		metadata_b = 1
   $ bpftool prog --metadata --json --pretty
   [{
           "id": 6,
   [...]
           "btf_id": 4,
           "metadata": {
               "metadata_a": "foo",
               "metadata_b": 1
           }
       }
   ]
---
  tools/bpf/bpftool/json_writer.c |   6 ++
  tools/bpf/bpftool/json_writer.h |   3 +
  tools/bpf/bpftool/main.c        |  10 +++
  tools/bpf/bpftool/main.h        |   1 +
  tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
  5 files changed, 155 insertions(+)
tools/bpf/bpftool/json_writer.c +6 -0
index 86501cd3c763..7fea83bedf48 100644
@@ -119,6 +119,12 @@ void jsonw_pretty(json_writer_t *self, bool on)
  	self->pretty = on;
  }
 
+void jsonw_reset(json_writer_t *self)
+{
+	assert(self->depth == 0);
+	self->sep = '\0';
+}
+
  /* Basic blocks */
  static void jsonw_begin(json_writer_t *self, int c)
  {
tools/bpf/bpftool/json_writer.h +3 -0
index 35cf1f00f96c..8ace65cdb92f 100644
@@ -27,6 +27,9 @@ void jsonw_destroy(json_writer_t **self_p);
  /* Cause output to have pretty whitespace */
  void jsonw_pretty(json_writer_t *self, bool on);
 
+/* Reset separator to create new JSON */
+void jsonw_reset(json_writer_t *self);
+
  /* Add property name */
  void jsonw_name(json_writer_t *self, const char *name);
 
tools/bpf/bpftool/main.c +10 -0
index 4a191fcbeb82..a681d568cfa7 100644
@@ -28,6 +28,7 @@ bool show_pinned;
  bool block_mount;
  bool verifier_logs;
  bool relaxed_maps;
+bool dump_metadata;
  struct pinned_obj_table prog_table;
  struct pinned_obj_table map_table;
  struct pinned_obj_table link_table;
@@ -351,6 +352,10 @@ static int do_batch(int argc, char **argv)
  	return err;
  }
 
+enum bpftool_longonly_opts {
+	OPT_METADATA = 256,
+};
+
  int main(int argc, char **argv)
  {
  	static const struct option options[] = {
@@ -362,6 +367,7 @@ int main(int argc, char **argv)
  		{ "mapcompat",	no_argument,	NULL,	'm' },
  		{ "nomount",	no_argument,	NULL,	'n' },
  		{ "debug",	no_argument,	NULL,	'd' },
+		{ "metadata",	no_argument,	NULL,	OPT_METADATA },
  		{ 0 }
  	};
  	int opt, ret;
@@ -371,6 +377,7 @@ int main(int argc, char **argv)
  	json_output = false;
  	show_pinned = false;
  	block_mount = false;
+	dump_metadata = false;
  	bin_name = argv[0];
 
  	hash_init(prog_table.table);
@@ -412,6 +419,9 @@ int main(int argc, char **argv)
  			libbpf_set_print(print_all_levels);
  			verifier_logs = true;
  			break;
+		case OPT_METADATA:
+			dump_metadata = true;
+			break;
  		default:
  			p_err("unrecognized option '%s'", argv[optind - 1]);
  			if (json_output)
tools/bpf/bpftool/main.h +1 -0
index c46e52137b87..8750758e9150 100644
@@ -90,6 +90,7 @@ extern bool show_pids;
  extern bool block_mount;
  extern bool verifier_logs;
  extern bool relaxed_maps;
+extern bool dump_metadata;
  extern struct pinned_obj_table prog_table;
  extern struct pinned_obj_table map_table;
  extern struct pinned_obj_table link_table;
tools/bpf/bpftool/prog.c +5 -0
index d393eb8263a6..ee767b8d90fb 100644
@@ -151,6 +151,135 @@ static void show_prog_maps(int fd, __u32 num_maps)
  	}
  }
 
+static void show_prog_metadata(int fd, __u32 num_maps)
+{
+	struct bpf_prog_info prog_info = {};
+	struct bpf_map_info map_info = {};
+	__u32 prog_info_len = sizeof(prog_info);

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 168

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+	__u32 map_info_len = sizeof(map_info);
+	__u32 map_ids[num_maps];
+	void *value = NULL;
+	struct btf *btf = NULL;
+	const struct btf_type *t_datasec, *t_var;
+	struct btf_var_secinfo *vsi;
+	int key = 0;
+	unsigned int i, vlen;
+	int map_fd;
+	int err;

try to follow reverse christmas tree coding styple?

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 175

Quoted 7 lines
tools/bpf/bpftool/prog.c +7 -0
+
+	prog_info.nr_map_ids = num_maps;
+	prog_info.map_ids = ptr_to_u64(map_ids);
+
+	err = bpf_obj_get_info_by_fd(fd, &prog_info, &prog_info_len);
+	if (err || !prog_info.nr_map_ids)
+		return;

print out something for "err" case and "!prog_info.nr_map_ids" case? The same for some other below returns.

Quoted 32 lines
tools/bpf/bpftool/prog.c +32 -0
+
+	for (i = 0; i < prog_info.nr_map_ids; i++) {
+		map_fd = bpf_map_get_fd_by_id(map_ids[i]);
+		if (map_fd < 0)
+			return;
+
+		err = bpf_obj_get_info_by_fd(map_fd, &map_info, &map_info_len);
+		if (err)
+			goto out_close;
+
+		if (map_info.type != BPF_MAP_TYPE_ARRAY)
+			goto next_map;
+		if (map_info.key_size != sizeof(int))
+			goto next_map;
+		if (map_info.max_entries != 1)
+			goto next_map;
+		if (!map_info.btf_value_type_id)
+			goto next_map;
+		if (!strstr(map_info.name, ".metadata"))
+			goto next_map;
+
+		goto found;
+
+next_map:
+		close(map_fd);
+	}
+
+	return;
+
+found:
+	value = malloc(map_info.value_size);
+	if (!value)
Quoted 8 lines

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 211

Quoted 4 lines
tools/bpf/bpftool/prog.c +4 -0
+		goto out_close;
+
+	if (bpf_map_lookup_elem(map_fd, &key, value))
+		goto out_free;

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

Yeah, please put in a libbpf accessor as well; I would like to use this from libxdp - without a skeleton :)

-Toke

#14 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 9 lines · Toke Høiland-Jørgensen
Quoted 3 lines · Yonghong Song

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

Yeah, please put in a libbpf accessor as well; I would like to use this from libxdp - without a skeleton :)

-Toke

I don't think I have an idea on a good API in libbpf that could be used to get the metadata of an existing program in kernel, that could be reused by bpftool without duplicating all the code. Maybe we can discuss this in a follow up series?

YiFei Zhu

#15 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 9 lines · YiFei Zhu
Quoted 8 lines · Toke Høiland-Jørgensen
Quoted 3 lines · Yonghong Song

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

Yeah, please put in a libbpf accessor as well; I would like to use this from libxdp - without a skeleton :)

Quoted 6 lines
Quoted 1 line

-Toke

I don't think I have an idea on a good API in libbpf that could be used to get the metadata of an existing program in kernel, that could be reused by bpftool without duplicating all the code. Maybe we can discuss this in a follow up series?

I think the most important part is getting a reference to the metadata map. So a function that basically does what the top half of what your show_prog_metadata() function does: given a prog fd, walk the map ids, check if any of them looks like a metadata map, and if so return the map fd.

Should be pretty straight-forward to reuse between bpftool/libbpf, no?

-Toke

#16 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 19 lines · Toke H�iland-J�rgensen
Quoted 1 line · YiFei Zhu

On Fri, Aug 21, 2020 at 3:58 AM Toke H�iland-J�rgensen

Quoted 8 lines · <[email protected]> wrote:
Quoted 8 lines
Quoted 3 lines · Yonghong Song

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

Yeah, please put in a libbpf accessor as well; I would like to use this from libxdp - without a skeleton :)

Quoted 6 lines
Quoted 1 line

-Toke

I don't think I have an idea on a good API in libbpf that could be used to get the metadata of an existing program in kernel, that could be reused by bpftool without duplicating all the code. Maybe we can discuss this in a follow up series?

Quoted 7 lines

I think the most important part is getting a reference to the metadata map. So a function that basically does what the top half of what your show_prog_metadata() function does: given a prog fd, walk the map ids, check if any of them looks like a metadata map, and if so return the map fd.

Should be pretty straight-forward to reuse between bpftool/libbpf, no?

Sounds good, I'll be taking over this patch series as YiFei's internship has ended. I'll try to address that.

#17 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 26 lines · [email protected] writes:
Quoted 24 lines · Toke H�iland-J�rgensen
Quoted 1 line · YiFei Zhu

On Fri, Aug 21, 2020 at 3:58 AM Toke H�iland-J�rgensen

Quoted 8 lines · <[email protected]> wrote:
Quoted 8 lines
Quoted 3 lines · Yonghong Song

Not sure whether we need formal libbpf API to access metadata or not. This may help other applications too. But we can delay until it is necessary.

Yeah, please put in a libbpf accessor as well; I would like to use this from libxdp - without a skeleton :)

Quoted 6 lines
Quoted 1 line

-Toke

I don't think I have an idea on a good API in libbpf that could be used to get the metadata of an existing program in kernel, that could be reused by bpftool without duplicating all the code. Maybe we can discuss this in a follow up series?

I think the most important part is getting a reference to the metadata map. So a function that basically does what the top half of what your show_prog_metadata() function does: given a prog fd, walk the map ids, check if any of them looks like a metadata map, and if so return the map fd.

Quoted 3 lines
Quoted 1 line

Should be pretty straight-forward to reuse between bpftool/libbpf, no?

Sounds good, I'll be taking over this patch series as YiFei's internship has ended. I'll try to address that.

Great, thanks! :)

-Toke

#18 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

In reply to YiFei Zhu
Quoted 42 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

  $ bpftool prog --metadata
  6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
  [...]
        btf_id 4
        metadata:
                metadata_a = "foo"
                metadata_b = 1
  $ bpftool prog --metadata --json --pretty
  [{
          "id": 6,
  [...]
          "btf_id": 4,
          "metadata": {
              "metadata_a": "foo",
              "metadata_b": 1
          }
      }
  ]
---
 tools/bpf/bpftool/json_writer.c |   6 ++
 tools/bpf/bpftool/json_writer.h |   3 +
 tools/bpf/bpftool/main.c        |  10 +++
 tools/bpf/bpftool/main.h        |   1 +
 tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
 5 files changed, 155 insertions(+)

[...]

Quoted 8 lines
tools/bpf/bpftool/prog.c +8 -0
+       for (i = 0; i < prog_info.nr_map_ids; i++) {
+               map_fd = bpf_map_get_fd_by_id(map_ids[i]);
+               if (map_fd < 0)
+                       return;
+
+               err = bpf_obj_get_info_by_fd(map_fd, &map_info, &map_info_len);
+               if (err)
+                       goto out_close;

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 194

Quoted 10 lines
tools/bpf/bpftool/prog.c +10 -0
+
+               if (map_info.type != BPF_MAP_TYPE_ARRAY)
+                       goto next_map;
+               if (map_info.key_size != sizeof(int))
+                       goto next_map;
+               if (map_info.max_entries != 1)
+                       goto next_map;
+               if (!map_info.btf_value_type_id)
+                       goto next_map;
+               if (!strstr(map_info.name, ".metadata"))

This substring check sucks. Let's make libbpf call this map strictly ".metadata". Current convention of "some part of object name" + "." + {rodata,data,bss} is extremely confusing. In practice it's something incomprehensible and "unguessable" like "test_pr.rodata". I think it makes sense to call them just ".data", ".rodata", ".bss", and ".metadata". But that might break existing apps that do lookups based on map name (and might break skeleton as it is today, not sure). But let's at least start with ".metadata", as it's a new map and we can get it right from the start.

Quoted 11 lines
tools/bpf/bpftool/prog.c +11 -0
+                       goto next_map;
+
+               goto found;
+
+next_map:
+               close(map_fd);
+       }
+
+       return;
+
+found:

[...]

#19 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 66 lines · Andrii Nakryiko
Quoted 42 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

  $ bpftool prog --metadata
  6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
  [...]
        btf_id 4
        metadata:
                metadata_a = "foo"
                metadata_b = 1
  $ bpftool prog --metadata --json --pretty
  [{
          "id": 6,
  [...]
          "btf_id": 4,
          "metadata": {
              "metadata_a": "foo",
              "metadata_b": 1
          }
      }
  ]
---
 tools/bpf/bpftool/json_writer.c |   6 ++
 tools/bpf/bpftool/json_writer.h |   3 +
 tools/bpf/bpftool/main.c        |  10 +++
 tools/bpf/bpftool/main.h        |   1 +
 tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
 5 files changed, 155 insertions(+)

[...]

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps)

Quoted 6 lines
tools/bpf/bpftool/prog.c +6 -0
+       for (i = 0; i < prog_info.nr_map_ids; i++) {
+               map_fd = bpf_map_get_fd_by_id(map_ids[i]);
+               if (map_fd < 0)
+                       return;
+
+               err = bpf_obj_get_info_by_fd(map_fd, &map_info,  

&map_info_len);

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 194

Quoted 12 lines
tools/bpf/bpftool/prog.c +12 -0
+               if (err)
+                       goto out_close;
+
+               if (map_info.type != BPF_MAP_TYPE_ARRAY)
+                       goto next_map;
+               if (map_info.key_size != sizeof(int))
+                       goto next_map;
+               if (map_info.max_entries != 1)
+                       goto next_map;
+               if (!map_info.btf_value_type_id)
+                       goto next_map;
+               if (!strstr(map_info.name, ".metadata"))
Quoted 9 lines

This substring check sucks. Let's make libbpf call this map strictly ".metadata". Current convention of "some part of object name" + "." + {rodata,data,bss} is extremely confusing. In practice it's something incomprehensible and "unguessable" like "test_pr.rodata". I think it makes sense to call them just ".data", ".rodata", ".bss", and ".metadata". But that might break existing apps that do lookups based on map name (and might break skeleton as it is today, not sure). But let's at least start with ".metadata", as it's a new map and we can get it right from the start.

Isn't it bad from the consistency point of view? Even if it's bad, at least it's consistent :-/

#20 lore raw

Re: [PATCH bpf-next 4/5] bpftool: support dumping metadata

Quoted 77 lines · On Fri, Aug 28, 2020 at 9:59 AM <[email protected]> wrote:
Quoted 66 lines · Andrii Nakryiko
Quoted 42 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

Added a flag "--metadata" to bpftool prog list to dump the metadata contents. For some formatting some BTF code is put directly in the metadata dumping. Sanity checks on the map and the kind of the btf_type to make sure we are actually dumping what we are expecting.

A helper jsonw_reset is added to json writer so we can reuse the same json writer without having extraneous commas.

Sample output:

  $ bpftool prog --metadata
  6: cgroup_skb  name prog  tag bcf7977d3b93787c  gpl
  [...]
        btf_id 4
        metadata:
                metadata_a = "foo"
                metadata_b = 1
  $ bpftool prog --metadata --json --pretty
  [{
          "id": 6,
  [...]
          "btf_id": 4,
          "metadata": {
              "metadata_a": "foo",
              "metadata_b": 1
          }
      }
  ]
---
 tools/bpf/bpftool/json_writer.c |   6 ++
 tools/bpf/bpftool/json_writer.h |   3 +
 tools/bpf/bpftool/main.c        |  10 +++
 tools/bpf/bpftool/main.h        |   1 +
 tools/bpf/bpftool/prog.c        | 135 ++++++++++++++++++++++++++++++++
 5 files changed, 155 insertions(+)

[...]

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps)

Quoted 6 lines
tools/bpf/bpftool/prog.c +6 -0
+       for (i = 0; i < prog_info.nr_map_ids; i++) {
+               map_fd = bpf_map_get_fd_by_id(map_ids[i]);
+               if (map_fd < 0)
+                       return;
+
+               err = bpf_obj_get_info_by_fd(map_fd, &map_info,

&map_info_len);

tools/bpf/bpftool/prog.c static void show_prog_maps(int fd, __u32 num_maps) · line 194

Quoted 12 lines
tools/bpf/bpftool/prog.c +12 -0
+               if (err)
+                       goto out_close;
+
+               if (map_info.type != BPF_MAP_TYPE_ARRAY)
+                       goto next_map;
+               if (map_info.key_size != sizeof(int))
+                       goto next_map;
+               if (map_info.max_entries != 1)
+                       goto next_map;
+               if (!map_info.btf_value_type_id)
+                       goto next_map;
+               if (!strstr(map_info.name, ".metadata"))
Quoted 9 lines

This substring check sucks. Let's make libbpf call this map strictly ".metadata". Current convention of "some part of object name" + "." + {rodata,data,bss} is extremely confusing. In practice it's something incomprehensible and "unguessable" like "test_pr.rodata". I think it makes sense to call them just ".data", ".rodata", ".bss", and ".metadata". But that might break existing apps that do lookups based on map name (and might break skeleton as it is today, not sure). But let's at least start with ".metadata", as it's a new map and we can get it right from the start.

Quoted 2 lines

Isn't it bad from the consistency point of view? Even if it's bad, at least it's consistent :-/

Just because we made a mistake once, doesn't mean we need to keep making it. ".metadata" is 9 characters already, which leaves 6 characters for object name prefix, that's not a lot of useful information anyway. As I said, we should probably fix it for other global data maps as well, but we will have to do it gradually. For .metadata we can do a nice and clean ".metadata" immediately, no need to jump through migration and deprecation hoops.

Also, how is "test_vml.bss" consistent with "test_v.metadata"?

author
#21 lore raw

PATCH 5/5 selftests/bpf: Test bpftool loading and dumping metadata

In reply to YiFei Zhu

From: YiFei Zhu <[email protected]>

This is a simple test to check that loading and dumping metadata works, whether or not metadata contents are used by the program.

---
 tools/testing/selftests/bpf/Makefile          |  3 +-
 .../selftests/bpf/progs/metadata_unused.c     | 15 ++++
 .../selftests/bpf/progs/metadata_used.c       | 15 ++++
 .../selftests/bpf/test_bpftool_metadata.sh    | 82 +++++++++++++++++++
 4 files changed, 114 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/progs/metadata_unused.c
 create mode 100644 tools/testing/selftests/bpf/progs/metadata_used.c
 create mode 100755 tools/testing/selftests/bpf/test_bpftool_metadata.sh
tools/testing/selftests/bpf/Makefile +2 -1
index a83b5827532f..04e56c6843c6 100644
@@ -68,7 +68,8 @@ TEST_PROGS := test_kmod.sh \
 	test_tc_edt.sh \
 	test_xdping.sh \
 	test_bpftool_build.sh \
-	test_bpftool.sh
+	test_bpftool.sh \
+	test_bpftool_metadata.sh \
 
 TEST_PROGS_EXTENDED := with_addr.sh \
 	with_tunnels.sh \
tools/testing/selftests/bpf/progs/metadata_unused.c new file +15 -0
new file mode 100644
index 000000000000..523b3c332426
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+char metadata_a[] SEC(".metadata") = "foo";
+int metadata_b SEC(".metadata") = 1;
+
+SEC("cgroup_skb/egress")
+int prog(struct xdp_md *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/progs/metadata_used.c new file +15 -0
new file mode 100644
index 000000000000..59785404f7bb
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+char metadata_a[] SEC(".metadata") = "bar";
+int metadata_b SEC(".metadata") = 2;
+
+SEC("cgroup_skb/egress")
+int prog(struct xdp_md *ctx)
+{
+	return metadata_b ? 1 : 0;
+}
+
+char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/test_bpftool_metadata.sh new file +82 -0

82 lines not on this page · raw message

Signature · 1 line
2.28.0
#22 lore raw

Re: [PATCH bpf-next 5/5] selftests/bpf: Test bpftool loading and dumping metadata

Quoted 21 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

This is a simple test to check that loading and dumping metadata works, whether or not metadata contents are used by the program.

---
  tools/testing/selftests/bpf/Makefile          |  3 +-
  .../selftests/bpf/progs/metadata_unused.c     | 15 ++++
  .../selftests/bpf/progs/metadata_used.c       | 15 ++++
  .../selftests/bpf/test_bpftool_metadata.sh    | 82 +++++++++++++++++++
  4 files changed, 114 insertions(+), 1 deletion(-)

create mode 100644 tools/testing/selftests/bpf/progs/metadata_unused.c create mode 100644 tools/testing/selftests/bpf/progs/metadata_used.c create mode 100755 tools/testing/selftests/bpf/test_bpftool_metadata.sh

tools/testing/selftests/bpf/Makefile +0 -0
index a83b5827532f..04e56c6843c6 100644

tools/testing/selftests/bpf/Makefile TEST_PROGS := test_kmod.sh \ · line 72

Quoted 6 lines
tools/testing/selftests/bpf/Makefile +2 -1
@@ -68,7 +68,8 @@ TEST_PROGS := test_kmod.sh \
  	test_tc_edt.sh \
  	test_xdping.sh \
  	test_bpftool_build.sh \
-	test_bpftool.sh
+	test_bpftool.sh \
+	test_bpftool_metadata.sh \

This is mostly testing bpftool side.
We should add testing to test_progs too as it is what most developer runs. If you add skeleton support for metadata, similar to bss, it will both make user interface easy and make testing easy.

Quoted 24 lines
tools/testing/selftests/bpf/Makefile +0 -0
  TEST_PROGS_EXTENDED := with_addr.sh \
  	with_tunnels.sh \
tools/testing/selftests/bpf/progs/metadata_unused.c new file +15 -0
new file mode 100644
index 000000000000..523b3c332426
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+char metadata_a[] SEC(".metadata") = "foo";
+int metadata_b SEC(".metadata") = 1;
+
+SEC("cgroup_skb/egress")
+int prog(struct xdp_md *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";

[...]

#23 lore raw

Re: [PATCH bpf-next 5/5] selftests/bpf: Test bpftool loading and dumping metadata

Quoted 20 lines · Yonghong Song
Quoted 16 lines · YiFei Zhu

From: YiFei Zhu <[email protected]>

This is a simple test to check that loading and dumping metadata works, whether or not metadata contents are used by the program.

---
  tools/testing/selftests/bpf/Makefile          |  3 +-
  .../selftests/bpf/progs/metadata_unused.c     | 15 ++++
  .../selftests/bpf/progs/metadata_used.c       | 15 ++++
  .../selftests/bpf/test_bpftool_metadata.sh    | 82 +++++++++++++++++++
  4 files changed, 114 insertions(+), 1 deletion(-)

create mode 100644 tools/testing/selftests/bpf/progs/metadata_unused.c create mode 100644 tools/testing/selftests/bpf/progs/metadata_used.c create mode 100755 tools/testing/selftests/bpf/test_bpftool_metadata.sh

Quoted 17 lines

tools/testing/selftests/bpf/Makefile TEST_PROGS := test_kmod.sh \ · line 72

Quoted 11 lines
tools/testing/selftests/bpf/Makefile +2 -1
index a83b5827532f..04e56c6843c6 100644
@@ -68,7 +68,8 @@ TEST_PROGS := test_kmod.sh \
      test_tc_edt.sh \
      test_xdping.sh \
      test_bpftool_build.sh \
-     test_bpftool.sh
+     test_bpftool.sh \
+     test_bpftool_metadata.sh \

This is mostly testing bpftool side.
We should add testing to test_progs too as it is what most developer runs. If you add skeleton support for metadata, similar to bss, it will both make user interface easy and make testing easy.

I concur. It also seems that program code can use metadata variables just like .rodata variables (e.g., for debug logging, etc), so we need to add tests exercising that ability as well.

Quoted 25 lines
Quoted 24 lines
tools/testing/selftests/bpf/Makefile +0 -0
  TEST_PROGS_EXTENDED := with_addr.sh \
      with_tunnels.sh \
tools/testing/selftests/bpf/progs/metadata_unused.c new file +15 -0
new file mode 100644
index 000000000000..523b3c332426
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+char metadata_a[] SEC(".metadata") = "foo";
+int metadata_b SEC(".metadata") = 1;
+
+SEC("cgroup_skb/egress")
+int prog(struct xdp_md *ctx)
+{
+     return 0;
+}
+
+char _license[] SEC("license") = "GPL";

[...]