All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alan Maguire <alan.maguire@oracle.com>
To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org
Cc: kafai@fb.com, songliubraving@fb.com, yhs@fb.com,
	john.fastabend@gmail.com, kpsingh@kernel.org, morbo@google.com,
	shuah@kernel.org, bpf@vger.kernel.org, netdev@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
	Alan Maguire <alan.maguire@oracle.com>
Subject: [PATCH v2 bpf-next 3/4] libbpf: BTF dumper support for typed data
Date: Sun, 17 Jan 2021 22:16:03 +0000	[thread overview]
Message-ID: <1610921764-7526-4-git-send-email-alan.maguire@oracle.com> (raw)
In-Reply-To: <1610921764-7526-1-git-send-email-alan.maguire@oracle.com>

Add a BTF dumper for typed data, so that the user can dump a typed
version of the data provided.

The API is

int btf_dump__emit_type_data(struct btf_dump *d, __u32 id,
                             const struct btf_dump_emit_type_data_opts *opts,
                             void *data);

...where the id is the BTF id of the data pointed to by the "void *"
argument; for example the BTF id of "struct sk_buff" for a
"struct skb *" data pointer.  Options supported are

 - a starting indent level (indent_lvl)
 - a set of boolean options to control dump display, similar to those
   used for BPF helper bpf_snprintf_btf().  Options are
        - compact : omit newlines and other indentation
        - noname: omit member names
        - zero: show zero-value members

Default output format is identical to that dumped by bpf_snprintf_btf(),
for example a "struct sk_buff" representation would look like this:

struct sk_buff){
 (union){
  (struct){
   .next = (struct sk_buff *)0xffffffffffffffff,
   .prev = (struct sk_buff *)0xffffffffffffffff,
   (union){
    .dev = (struct net_device *)0xffffffffffffffff,
    .dev_scratch = (long unsigned int)18446744073709551615,
   },
  },
...

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 tools/lib/bpf/btf.h      |  17 +
 tools/lib/bpf/btf_dump.c | 974 +++++++++++++++++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.map |   5 +
 3 files changed, 996 insertions(+)

diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 0c48f2e..7937124 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -180,6 +180,23 @@ struct btf_dump_emit_type_decl_opts {
 btf_dump__emit_type_decl(struct btf_dump *d, __u32 id,
 			 const struct btf_dump_emit_type_decl_opts *opts);
 
+
+struct btf_dump_emit_type_data_opts {
+	/* size of this struct, for forward/backward compatibility */
+	size_t sz;
+	int indent_level;
+	/* below match "show" flags for bpf_show_snprintf() */
+	bool compact;
+	bool noname;
+	bool zero;
+};
+#define btf_dump_emit_type_data_opts__last_field zero
+
+LIBBPF_API int
+btf_dump__emit_type_data(struct btf_dump *d, __u32 id,
+			 const struct btf_dump_emit_type_data_opts *opts,
+			 void *data);
+
 /*
  * A set of helpers for easier BTF types handling
  */
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index 2f9d685..04d604f 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -10,6 +10,8 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
+#include <endian.h>
 #include <errno.h>
 #include <linux/err.h>
 #include <linux/btf.h>
@@ -19,14 +21,31 @@
 #include "libbpf.h"
 #include "libbpf_internal.h"
 
+#define BITS_PER_BYTE			8
+#define BITS_PER_U128			(sizeof(__u64) * BITS_PER_BYTE * 2)
+#define BITS_PER_BYTE_MASK		(BITS_PER_BYTE - 1)
+#define BITS_PER_BYTE_MASKED(bits)	((bits) & BITS_PER_BYTE_MASK)
+#define BITS_ROUNDDOWN_BYTES(bits)	((bits) >> 3)
+#define BITS_ROUNDUP_BYTES(bits) \
+	(BITS_ROUNDDOWN_BYTES(bits) + !!BITS_PER_BYTE_MASKED(bits))
+
 static const char PREFIXES[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t";
 static const size_t PREFIX_CNT = sizeof(PREFIXES) - 1;
 
+
 static const char *pfx(int lvl)
 {
 	return lvl >= PREFIX_CNT ? PREFIXES : &PREFIXES[PREFIX_CNT - lvl];
 }
 
+static const char SPREFIXES[] = "                         ";
+static const size_t SPREFIX_CNT = sizeof(SPREFIXES) - 1;
+
+static const char *spfx(int lvl)
+{
+	return lvl >= SPREFIX_CNT ? SPREFIXES : &SPREFIXES[SPREFIX_CNT - lvl];
+}
+
 enum btf_dump_type_order_state {
 	NOT_ORDERED,
 	ORDERING,
@@ -53,6 +72,49 @@ struct btf_dump_type_aux_state {
 	__u8 referenced: 1;
 };
 
+#define BTF_DUMP_DATA_MAX_NAME_LEN	256
+
+/*
+ * Common internal data for BTF type data dump operations.
+ *
+ * The implementation here is similar to that in kernel/bpf/btf.c
+ * that supports the bpf_snprintf_btf() helper, so any bugs in
+ * type data dumping here are likely in that code also.
+ *
+ * One challenge with showing nested data is we want to skip 0-valued
+ * data, but in order to figure out whether a nested object is all zeros
+ * we need to walk through it.  As a result, we need to make two passes
+ * when handling structs, unions and arrays; the first path simply looks
+ * for nonzero data, while the second actually does the display.  The first
+ * pass is signalled by state.depth_check being set, and if we
+ * encounter a non-zero value we set state.depth_to_show to the depth
+ * at which we encountered it.  When we have completed the first pass,
+ * we will know if anything needs to be displayed if
+ * state.depth_to_show > state.depth.  See btf_dump_emit_[struct,array]_data()
+ * for the implementation of this.
+ *
+ */
+struct btf_dump_data {
+	bool compact;
+	bool noname;
+	bool zero;
+	__u8 indent_lvl;	/* base indent level */
+	/* below are used during iteration */
+	struct {
+		__u8 depth;
+		__u8 depth_to_show;
+		__u8 depth_check;
+		__u8 array_member:1,
+		     array_terminated:1;
+		__u16 array_encoding;
+		__u32 type_id;
+		const struct btf_type *type;
+		const struct btf_member *member;
+		char name[BTF_DUMP_DATA_MAX_NAME_LEN];
+		int err;
+	} state;
+};
+
 struct btf_dump {
 	const struct btf *btf;
 	const struct btf_ext *btf_ext;
@@ -89,6 +151,10 @@ struct btf_dump {
 	 * name occurrences
 	 */
 	struct hashmap *ident_names;
+	/*
+	 * data for typed display.
+	 */
+	struct btf_dump_data data;
 };
 
 static size_t str_hash_fn(const void *key, void *ctx)
@@ -1438,3 +1504,911 @@ static const char *btf_dump_ident_name(struct btf_dump *d, __u32 id)
 {
 	return btf_dump_resolve_name(d, id, d->ident_names);
 }
+
+static void __btf_dump_emit_type_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset);
+
+static const struct btf_type *skip_mods(const struct btf *btf,
+					__u32 id, __u32 *res_id)
+{
+	const struct btf_type *t = btf__type_by_id(btf, id);
+
+	while (btf_is_mod(t)) {
+		id = t->type;
+		t = btf__type_by_id(btf, t->type);
+	}
+
+	if (res_id)
+		*res_id = id;
+
+	return t;
+}
+
+#define BTF_MAX_ITER		10
+#define BTF_KIND_BIT(kind)	(1ULL << kind)
+
+/*
+ * Populate dump->data.state.name with type name information.
+ * Format of type name is
+ *
+ *	[.member_name = ] (type_name)
+ */
+static const char *btf_dump_data_name(struct btf_dump *d)
+{
+	/* BTF_MAX_ITER array suffixes "[]" */
+	const char *array_suffixes = "[][][][][][][][][][]";
+	const char *array_suffix = &array_suffixes[strlen(array_suffixes)];
+	/* BTF_MAX_ITER pointer suffixes "*" */
+	const char *ptr_suffixes = "**********";
+	const char *ptr_suffix = &ptr_suffixes[strlen(ptr_suffixes)];
+	const char *name = NULL, *prefix = "", *parens = "";
+	const struct btf_member *m = d->data.state.member;
+	const struct btf_type *t = d->data.state.type;
+	const struct btf_array *array;
+	__u32 id = d->data.state.type_id;
+	const char *member = NULL;
+	bool show_member = false;
+	__u64 kinds = 0;
+	int i;
+
+	d->data.state.name[0] = '\0';
+
+	/*
+	 * Don't show type name if we're showing an array member;
+	 * in that case we show the array type so don't need to repeat
+	 * ourselves for each member.
+	 */
+	if (d->data.state.array_member)
+		return "";
+
+	/* Retrieve member name, if any. */
+	if (m) {
+		member = btf_name_of(d, m->name_off);
+		show_member = strlen(member) > 0;
+		id = m->type;
+	}
+
+	/*
+	 * Start with type_id, as we have resolved the struct btf_type *
+	 * via btf_dump_emit_modifier_data() past the parent typedef to the
+	 * child struct, int etc it is defined as.  In such cases, the type_id
+	 * still represents the starting type while the struct btf_type *
+	 * in our d->data.state points at the resolved type of the typedef.
+	 */
+	t = btf__type_by_id(d->btf, id);
+	if (!t)
+		return "";
+
+       /*
+	* The goal here is to build up the right number of pointer and
+	* array suffixes while ensuring the type name for a typedef
+	* is represented.  Along the way we accumulate a list of
+	* BTF kinds we have encountered, since these will inform later
+	* display; for example, pointer types will not require an
+	* opening "{" for struct, we will just display the pointer value.
+	*
+	* We also want to accumulate the right number of pointer or array
+	* indices in the format string while iterating until we get to
+	* the typedef/pointee/array member target type.
+	*
+	* We start by pointing at the end of pointer and array suffix
+	* strings; as we accumulate pointers and arrays we move the pointer
+	* or array string backwards so it will show the expected number of
+	* '*' or '[]' for the type.  BTF_SHOW_MAX_ITER of nesting of pointers
+	* and/or arrays and typedefs are supported as a precaution.
+	*
+	* We also want to get typedef name while proceeding to resolve
+	* type it points to so that we can add parentheses if it is a
+	* "typedef struct" etc.
+	*
+	* Qualifiers ("const", "volatile", "restrict") are simply skipped
+	* as they complicate simple type name display without adding much
+	* in the case of displaying a cast in front of the data to be
+	* displayed.
+	*/
+	for (i = 0; i < BTF_MAX_ITER; i++) {
+
+		switch (BTF_INFO_KIND(t->info)) {
+		case BTF_KIND_TYPEDEF:
+			if (!name)
+				name = btf_name_of(d, t->name_off);
+			kinds |= BTF_KIND_BIT(BTF_KIND_TYPEDEF);
+			id = t->type;
+			break;
+		case BTF_KIND_ARRAY:
+			kinds |= BTF_KIND_BIT(BTF_KIND_ARRAY);
+			parens = "[";
+			if (!t)
+				return "";
+			array = btf_array(t);
+			if (array_suffix > array_suffixes)
+				array_suffix -= 2;
+			id = array->type;
+			break;
+		case BTF_KIND_PTR:
+			kinds |= BTF_KIND_BIT(BTF_KIND_PTR);
+			if (ptr_suffix > ptr_suffixes)
+				ptr_suffix -= 1;
+			id = t->type;
+			break;
+		default:
+			id = 0;
+			break;
+		}
+		if (!id)
+			break;
+		t = skip_mods(d->btf, id, NULL);
+	}
+	/* We may not be able to represent this type; bail to be safe */
+	if (i == BTF_MAX_ITER) {
+		pr_warn("iters %d exceeded %d when displaying type name:[%u]\n",
+			i, BTF_MAX_ITER, id);
+		return "";
+	}
+
+	if (!name)
+		name = btf_name_of(d, t->name_off);
+
+	switch (BTF_INFO_KIND(t->info)) {
+	case BTF_KIND_STRUCT:
+	case BTF_KIND_UNION:
+		prefix = BTF_INFO_KIND(t->info) == BTF_KIND_STRUCT ?
+						   "struct" : "union";
+		/* if it's an array of struct/union, parens is already set */
+		if (!(kinds & (BTF_KIND_BIT(BTF_KIND_ARRAY))))
+			parens = "{";
+		break;
+	case BTF_KIND_ENUM:
+		prefix = "enum";
+		break;
+	default:
+		break;
+	}
+
+	/* pointer does not require parens */
+	if (kinds & BTF_KIND_BIT(BTF_KIND_PTR))
+		parens = "";
+	/* typedef does not require struct/union/enum prefix */
+	if (kinds & BTF_KIND_BIT(BTF_KIND_TYPEDEF))
+		prefix = "";
+
+	if (!name)
+		name = "";
+
+	/* Even if we don't want type name info, we want parentheses etc */
+	if (d->data.noname)
+		snprintf(d->data.state.name, sizeof(d->data.state.name), "%s",
+			 parens);
+	else
+		snprintf(d->data.state.name, sizeof(d->data.state.name),
+			 "%s%s%s(%s%s%s%s%s%s)%s",
+			 /* first 3 strings comprise ".member = " */
+			 show_member ? "." : "",
+			 show_member ? member : "",
+			 show_member ? " = " : "",
+			 /* ...next is our prefix (struct, enum, etc) */
+			 prefix,
+			 strlen(prefix) > 0 && strlen(name) > 0 ? " " : "",
+			 /* ...this is the type name itself */
+			 name,
+			 /* ...suffixed by the appropriate '*', '[]' suffixes */
+			 strlen(name) > 0 && strlen(ptr_suffix) > 0 ? " " : "",
+			 ptr_suffix,
+			 array_suffix, parens);
+
+	return d->data.state.name;
+}
+
+static const char *btf_dump_data_indent(struct btf_dump *d)
+{
+	if (d->data.compact)
+		return "";
+	return spfx(d->data.indent_lvl + d->data.state.depth);
+}
+
+static const char *btf_dump_data_newline(struct btf_dump *d)
+{
+	return d->data.compact ? "" : "\n";
+}
+
+static const char *btf_dump_data_delim(struct btf_dump *d)
+{
+	if (d->data.state.depth == 0)
+		return "";
+
+	if (d->data.compact &&
+	    d->data.state.type &&
+	    BTF_INFO_KIND(d->data.state.type->info) == BTF_KIND_UNION)
+		return "|";
+
+	return ",";
+}
+
+static void btf_dump_data_printf(struct btf_dump *d,
+				 const char *fmt, ...)
+{
+	va_list args;
+
+	/*
+	 * Just checking if there is non-zero data to display at this depth,
+	 * so nothing is displayed.
+	 */
+	if (d->data.state.depth_check)
+		return;
+	va_start(args, fmt);
+	d->printf_fn(d->opts.ctx, fmt, args);
+	va_end(args);
+}
+
+/* Macros are used here as btf_type_value[s]() prepends and appends
+ * format specifiers to the format specifier passed in; these do the work of
+ * adding indentation, delimiters etc while the caller simply has to specify
+ * the type value(s) in the format specifier + value(s).
+ */
+#define btf_dump_emit_type_value(d, fmt, value)				     \
+	do {								     \
+		if ((value) != 0 || d->data.zero ||			     \
+		    d->data.state.depth == 0) {				     \
+			btf_dump_data_printf(d, "%s%s" fmt "%s%s",	     \
+					     btf_dump_data_indent(d),	     \
+					     btf_dump_data_name(d),          \
+					     value,			     \
+					     btf_dump_data_delim(d),	     \
+					     btf_dump_data_newline(d));      \
+			if (d->data.state.depth >			     \
+			    d->data.state.depth_to_show)		     \
+				d->data.state.depth_to_show =		     \
+					d->data.state.depth;		     \
+		}							     \
+	} while (0)
+
+#define btf_dump_emit_type_values(d, fmt, ...)				\
+	do {								\
+		btf_dump_data_printf(d, "%s%s" fmt "%s%s",		\
+				     btf_dump_data_indent(d),		\
+				     btf_dump_data_name(d),		\
+				     __VA_ARGS__,			\
+				     btf_dump_data_delim(d),		\
+				     btf_dump_data_newline(d));		\
+		if (d->data.state.depth >				\
+		    d->data.state.depth_to_show)			\
+			d->data.state.depth_to_show =			\
+				d->data.state.depth;			\
+	} while (0)
+
+/* Set the type we are starting to show. */
+static void btf_dump_start_type(struct btf_dump *d,
+				const struct btf_type *t,
+				__u32 type_id)
+{
+	d->data.state.type = t;
+	d->data.state.type_id = type_id;
+	d->data.state.name[0] = '\0';
+}
+
+static void btf_dump_end_type(struct btf_dump *d)
+{
+	d->data.state.type = NULL;
+	d->data.state.type_id = 0;
+	d->data.state.name[0] = '\0';
+}
+
+static void btf_dump_start_aggr_type(struct btf_dump *d,
+				     const struct btf_type *t,
+				     __u32 type_id)
+{
+	btf_dump_start_type(d, t, type_id);
+
+	btf_dump_data_printf(d, "%s%s%s",
+			     btf_dump_data_indent(d),
+			     btf_dump_data_name(d),
+			     btf_dump_data_newline(d));
+	d->data.state.depth++;
+}
+
+static void btf_dump_end_aggr_type(struct btf_dump *d,
+				   const char *suffix)
+{
+	d->data.state.depth--;
+	btf_dump_data_printf(d, "%s%s%s%s",
+			     btf_dump_data_indent(d),
+			     suffix,
+			     btf_dump_data_delim(d),
+			     btf_dump_data_newline(d));
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_start_member(struct btf_dump *d,
+				  const struct btf_member *m)
+{
+	d->data.state.member = m;
+}
+
+static void btf_dump_start_array_member(struct btf_dump *d)
+{
+	d->data.state.array_member = 1;
+	btf_dump_start_member(d, NULL);
+}
+
+static void btf_dump_end_member(struct btf_dump *d)
+{
+	d->data.state.member = NULL;
+}
+
+static void btf_dump_end_array_member(struct btf_dump *d)
+{
+	d->data.state.array_member = 0;
+	btf_dump_end_member(d);
+}
+
+static void btf_dump_start_array_type(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 type_id,
+				      __u16 array_encoding)
+{
+	d->data.state.array_encoding = array_encoding;
+	d->data.state.array_terminated = 0;
+	btf_dump_start_aggr_type(d, t, type_id);
+}
+
+static void btf_dump_end_array_type(struct btf_dump *d)
+{
+	d->data.state.array_encoding = 0;
+	d->data.state.array_terminated = 0;
+	btf_dump_end_aggr_type(d, "]");
+}
+
+static void btf_dump_start_struct_type(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 type_id)
+{
+	btf_dump_start_aggr_type(d, t, type_id);
+}
+
+static void btf_dump_end_struct_type(struct btf_dump *d)
+{
+	btf_dump_end_aggr_type(d, "}");
+}
+
+static void btf_dump_emit_df_data(struct btf_dump *d,
+				  const struct btf_type *t,
+				  __u32 id,
+				  void *data,
+				  __u8 bits_offset)
+{
+	btf_dump_data_printf(d, "<unsupported kind:%u>",
+			     BTF_INFO_KIND(t->info));
+}
+
+static void btf_dump_emit_int128(struct btf_dump *d, void *data)
+{
+	/* data points to a __int128 number.
+	 * Suppose
+	 *	int128_num = *(__int128 *)data;
+	 * The below formulas shows what upper_num and lower_num represents:
+	 *     upper_num = int128_num >> 64;
+	 *     lower_num = int128_num & 0xffffffffFFFFFFFFULL;
+	 */
+	__u64 upper_num, lower_num;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	upper_num = *(__u64 *)data;
+	lower_num = *(__u64 *)(data + 8);
+#else
+	upper_num = *(__u64 *)(data + 8);
+	lower_num = *(__u64 *)data;
+#endif
+	if (upper_num == 0)
+		btf_dump_emit_type_value(d, "0x%llx", lower_num);
+	else
+		btf_dump_emit_type_values(d, "0x%llx%016llx", upper_num,
+					  lower_num);
+}
+
+static void btf_int128_shift(__u64 *print_num, __u16 left_shift_bits,
+			     __u16 right_shift_bits)
+{
+	__u64 upper_num, lower_num;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	upper_num = print_num[0];
+	lower_num = print_num[1];
+#else
+	upper_num = print_num[1];
+	lower_num = print_num[0];
+#endif
+
+	/* shake out un-needed bits by shift/or operations */
+	if (left_shift_bits >= 64) {
+		upper_num = lower_num << (left_shift_bits - 64);
+		lower_num = 0;
+	} else {
+		upper_num = (upper_num << left_shift_bits) |
+			    (lower_num >> (64 - left_shift_bits));
+		lower_num = lower_num << left_shift_bits;
+	}
+
+	if (right_shift_bits >= 64) {
+		lower_num = upper_num >> (right_shift_bits - 64);
+		upper_num = 0;
+	} else {
+		lower_num = (lower_num >> right_shift_bits) |
+			    (upper_num << (64 - right_shift_bits));
+		upper_num = upper_num >> right_shift_bits;
+	}
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	print_num[0] = upper_num;
+	print_num[1] = lower_num;
+#else
+	print_num[0] = lower_num;
+	print_num[1] = upper_num;
+#endif
+}
+
+static void btf_dump_emit_bitfield_data(struct btf_dump *d,
+					void *data,
+					__u8 bits_offset,
+					__u8 nr_bits)
+{
+	__u16 left_shift_bits, right_shift_bits;
+	__u8 nr_copy_bytes;
+	__u8 nr_copy_bits;
+	__u64 print_num[2] = {};
+
+	nr_copy_bits = nr_bits + bits_offset;
+	nr_copy_bytes = BITS_ROUNDUP_BYTES(nr_copy_bits);
+
+	memcpy(print_num, data, nr_copy_bytes);
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	left_shift_bits = bits_offset;
+#else
+	left_shift_bits = BITS_PER_U128 - nr_copy_bits;
+#endif
+	right_shift_bits = BITS_PER_U128 - nr_bits;
+
+	btf_int128_shift(print_num, left_shift_bits, right_shift_bits);
+	btf_dump_emit_int128(d, print_num);
+}
+
+static void btf_dump_emit_int_bits(struct btf_dump *d,
+				   const struct btf_type *t,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 int_data = btf_int(t);
+	__u8 nr_bits = BTF_INT_BITS(int_data);
+	__u8 total_bits_offset;
+
+	/*
+	 * bits_offset is at most 7.
+	 * BTF_INT_OFFSET() cannot exceed 128 bits.
+	 */
+	total_bits_offset = bits_offset + BTF_INT_OFFSET(int_data);
+	data += BITS_ROUNDDOWN_BYTES(total_bits_offset);
+	bits_offset = BITS_PER_BYTE_MASKED(total_bits_offset);
+	btf_dump_emit_bitfield_data(d, data, bits_offset, nr_bits);
+}
+
+static void btf_dump_emit_int_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 type_id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 int_data = btf_int(t);
+	__u8 encoding = BTF_INT_ENCODING(int_data);
+	bool sign = encoding & BTF_INT_SIGNED;
+	__u8 nr_bits = BTF_INT_BITS(int_data);
+
+	btf_dump_start_type(d, t, type_id);
+
+	if (bits_offset || BTF_INT_OFFSET(int_data) ||
+	    BITS_PER_BYTE_MASKED(nr_bits)) {
+		btf_dump_emit_int_bits(d, t, data, bits_offset);
+		goto out;
+	}
+
+	switch (nr_bits) {
+	case 128:
+		btf_dump_emit_int128(d, data);
+		break;
+	case 64:
+		if (sign)
+			btf_dump_emit_type_value(d, "%lld", *(__s64 *)data);
+		else
+			btf_dump_emit_type_value(d, "%llu", *(__u64 *)data);
+		break;
+	case 32:
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s32 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u32 *)data);
+		break;
+	case 16:
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s16 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u16 *)data);
+		break;
+	case 8:
+		if (d->data.state.array_encoding == BTF_INT_CHAR) {
+			/* check for null terminator */
+			if (d->data.state.array_terminated)
+				break;
+			if (*(char *)data == '\0') {
+				d->data.state.array_terminated = 1;
+				break;
+			}
+			if (isprint(*(char *)data)) {
+				btf_dump_emit_type_value(d, "'%c'",
+							 *(char *)data);
+				break;
+			}
+		}
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s8 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u8 *)data);
+		break;
+	default:
+		btf_dump_emit_int_bits(d, t, data, bits_offset);
+		break;
+	}
+out:
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_emit_modifier_data(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset)
+{
+	t = skip_mods_and_typedefs(d->btf, id, NULL);
+	__btf_dump_emit_type_data(d, t, id, data, bits_offset);
+}
+
+static void btf_dump_emit_var_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 linkage = btf_var(t)->linkage;
+
+	btf_dump_data_printf(d, "%s%s =",
+			     linkage ? "" : "static ",
+			     btf_name_of(d, t->name_off));
+	t = btf__type_by_id(d->btf, t->type);
+	__btf_dump_emit_type_data(d, t, t->type, data, bits_offset);
+}
+
+static void __btf_dump_emit_array_data(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 id,
+				       void *data,
+				       __u8 bits_offset)
+{
+	const struct btf_array *array = btf_array(t);
+	const struct btf_type *elem_type;
+	__u32 i, elem_size = 0, elem_type_id;
+	__u16 encoding = 0;
+
+	elem_type_id = array->type;
+	elem_type = skip_mods_and_typedefs(d->btf, elem_type_id, NULL);
+	if (elem_type && btf_has_size(elem_type))
+		elem_size = elem_type->size;
+
+	if (elem_type && btf_is_int(elem_type)) {
+		__u32 int_type = btf_int(elem_type);
+
+		encoding = BTF_INT_ENCODING(int_type);
+
+		/*
+		 * BTF_INT_CHAR encoding never seems to be set for
+		 * char arrays, so if size is 1 and element is
+		 * printable as a char, we'll do that.
+		 */
+		if (elem_size == 1)
+			encoding = BTF_INT_CHAR;
+	}
+
+	btf_dump_start_array_type(d, t, id, encoding);
+
+	if (!elem_type)
+		goto out;
+
+	for (i = 0; i < array->nelems; i++) {
+
+		btf_dump_start_array_member(d);
+
+		__btf_dump_emit_type_data(d, elem_type, elem_type_id,
+					  data, bits_offset);
+		data += elem_size;
+
+		btf_dump_end_array_member(d);
+
+		if (d->data.state.array_terminated)
+			break;
+	}
+out:
+	btf_dump_end_array_type(d);
+}
+
+static void btf_dump_emit_array_data(struct btf_dump *d,
+				     const struct btf_type *t,
+				     __u32 id,
+				     void *data,
+				     __u8 bits_offset)
+{
+	const struct btf_member *m = d->data.state.member;
+
+	/*
+	 * First check if any members would be shown (are non-zero).
+	 * See comments above "struct btf_dump_data" definition for more
+	 * details on how this works at a high-level.
+	 */
+	if (d->data.state.depth > 0 && !d->data.zero) {
+		if (!d->data.state.depth_check) {
+			d->data.state.depth_check = d->data.state.depth + 1;
+			d->data.state.depth_to_show = 0;
+		}
+		__btf_dump_emit_array_data(d, t, id, data, bits_offset);
+		d->data.state.member = m;
+
+		if (d->data.state.depth_check != d->data.state.depth + 1)
+			return;
+		d->data.state.depth_check = 0;
+
+		if (d->data.state.depth_to_show <= d->data.state.depth)
+			return;
+		/*
+		 * Reaching here indicates we have recursed and found
+		 * non-zero array member(s).
+		 */
+	}
+	__btf_dump_emit_array_data(d, t, id, data, bits_offset);
+}
+
+#define for_each_member(i, struct_type, member)			\
+	for (i = 0, member = btf_members(struct_type);		\
+	     i < btf_vlen(struct_type);				\
+	     i++, member++)
+
+static void __btf_dump_emit_struct_data(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset)
+{
+	const struct btf_member *member;
+	__u32 i;
+
+	btf_dump_start_struct_type(d, t, id);
+
+	for_each_member(i, t, member) {
+		const struct btf_type *member_type;
+		__u32 member_offset, bitfield_size;
+		__u32 bytes_offset;
+		__u8 bits8_offset;
+
+		member_type = btf__type_by_id(d->btf, member->type);
+		btf_dump_start_member(d, member);
+
+		member_offset = btf_member_bit_offset(t, i);
+		bitfield_size = btf_member_bitfield_size(t, i);
+		bytes_offset = BITS_ROUNDDOWN_BYTES(member_offset);
+		bits8_offset = BITS_PER_BYTE_MASKED(member_offset);
+		if (bitfield_size) {
+			btf_dump_start_type(d, member_type, member->type);
+			btf_dump_emit_bitfield_data(d,
+						    data + bytes_offset,
+						    bits8_offset,
+						    bitfield_size);
+			btf_dump_end_type(d);
+		} else {
+			__btf_dump_emit_type_data(d, member_type, member->type,
+					     data + bytes_offset, bits8_offset);
+		}
+		btf_dump_end_member(d);
+	}
+	btf_dump_end_struct_type(d);
+}
+
+static void btf_dump_emit_struct_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset)
+{
+	const struct btf_member *m = d->data.state.member;
+
+	/*
+	 * First check if any members would be shown (are non-zero).
+	 * See comments above "struct btf_dump_data" definition for more
+	 * details on how this works at a high-level.
+	 */
+	if (d->data.state.depth > 0 && !d->data.zero) {
+		if (!d->data.state.depth_check) {
+			d->data.state.depth_check = d->data.state.depth + 1;
+			d->data.state.depth_to_show = 0;
+		}
+		__btf_dump_emit_struct_data(d, t, id, data, bits_offset);
+		/* Restore saved member data here */
+		d->data.state.member = m;
+		if (d->data.state.depth_check != d->data.state.depth + 1)
+			return;
+		d->data.state.depth_check = 0;
+
+		if (d->data.state.depth_to_show <= d->data.state.depth)
+			return;
+		/*
+		 * Reaching here indicates we have recursed and found
+		 * non-zero child values.
+		 */
+	}
+
+	__btf_dump_emit_struct_data(d, t, id, data, bits_offset);
+}
+
+static void btf_dump_emit_ptr_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	btf_dump_start_type(d, t, id);
+
+	btf_dump_emit_type_value(d, "%p", *(void **)data);
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_emit_enum_data(struct btf_dump *d,
+				    const struct btf_type *t,
+				    __u32 id,
+				    void *data,
+				    __u8 bits_offset)
+{
+	const struct btf_enum *enums = btf_enum(t);
+	__s64 value;
+	__u16 i;
+
+	btf_dump_start_type(d, t, id);
+
+	switch (t->size) {
+	case 8:
+		value = *(__s64 *)data;
+		break;
+	case 4:
+		value = *(__s32 *)data;
+		break;
+	case 2:
+		value = *(__s16 *)data;
+		break;
+	case 1:
+		value = *(__s8 *)data;
+		break;
+	default:
+		pr_warn("unexpected size %d for enum, id:[%u]\n", t->size,
+			id);
+		d->data.state.err = -EINVAL;
+		return;
+	}
+
+	for (i = 0; i < btf_vlen(t); i++) {
+		if (value == enums[i].val) {
+			btf_dump_emit_type_value(d, "%s",
+						 btf_name_of(d,
+							     enums[i].name_off));
+			btf_dump_end_type(d);
+			return;
+		}
+	}
+
+	btf_dump_emit_type_value(d, "%d", value);
+	btf_dump_end_type(d);
+}
+
+#define for_each_vsi(i, struct_type, member)			\
+	for (i = 0, member = btf_var_secinfos(struct_type);	\
+	     i < btf_vlen(struct_type);				\
+	     i++, member++)
+
+static void btf_dump_emit_datasec_data(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 id,
+				       void *data,
+				       __u8 bits_offset)
+{
+	const struct btf_var_secinfo *vsi;
+	const struct btf_type *var;
+	__u32 i;
+
+	btf_dump_start_type(d, t, id);
+
+	btf_dump_emit_type_value(d, "section (\"%s\") = {",
+				 btf_name_of(d, t->name_off));
+	for_each_vsi(i, t, vsi) {
+		var = btf__type_by_id(d->btf, vsi->type);
+		if (i)
+			btf_dump_data_printf(d, ",");
+		__btf_dump_emit_type_data(d, var, vsi->type,
+					  data + vsi->offset,
+					  bits_offset);
+	}
+	btf_dump_end_type(d);
+}
+
+typedef void (*btf_dump_emit_kind_data)(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset);
+
+static btf_dump_emit_kind_data dump_emit_kind_data[NR_BTF_KINDS] = {
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_int_data,
+	&btf_dump_emit_ptr_data,
+	&btf_dump_emit_array_data,
+	&btf_dump_emit_struct_data,
+	&btf_dump_emit_struct_data,
+	&btf_dump_emit_enum_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_var_data,
+	&btf_dump_emit_datasec_data,
+};
+
+static void __btf_dump_emit_type_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset)
+{
+	dump_emit_kind_data[BTF_INFO_KIND(t->info)](d, t, id, data,
+						    bits_offset);
+}
+
+static void btf_dump_emit_type_data(struct btf_dump *d, __u32 id, void *data)
+{
+	const struct btf_type *t = btf__type_by_id(d->btf, id);
+
+	memset(&d->data.state, 0, sizeof(d->data.state));
+
+	if (!t) {
+		pr_warn("no type info, id [%u]\n", id);
+		d->data.state.err = -EINVAL;
+		return;
+	}
+
+	__btf_dump_emit_type_data(d, t, id, data, 0);
+}
+
+int btf_dump__emit_type_data(struct btf_dump *d, __u32 id,
+			     const struct btf_dump_emit_type_data_opts *opts,
+			     void *data)
+{
+	int err;
+
+	if (!OPTS_VALID(opts, btf_dump_emit_type_data_opts))
+		return -EINVAL;
+
+	d->data.indent_lvl = OPTS_GET(opts, indent_level, 0);
+	d->data.compact = OPTS_GET(opts, compact, false);
+	d->data.noname = OPTS_GET(opts, noname, false);
+	d->data.zero = OPTS_GET(opts, zero, false);
+	btf_dump_emit_type_data(d, id, data);
+	err = d->data.state.err;
+	memset(&d->data, 0, sizeof(d->data));
+	return err;
+}
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 1c0fd2d..b81c069 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -350,3 +350,8 @@ LIBBPF_0.3.0 {
 		xsk_setup_xdp_prog;
 		xsk_socket__update_xskmap;
 } LIBBPF_0.2.0;
+
+LIBBPF_0.4.0 {
+	global:
+		btf_dump__emit_type_data;
+} LIBBPF_0.3.0;
-- 
1.8.3.1


  parent reply	other threads:[~2021-01-17 22:25 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-17 22:16 [PATCH v2 bpf-next 0/4] libbpf: BTF dumper support for typed data Alan Maguire
2021-01-17 22:16 ` [PATCH v2 bpf-next 1/4] libbpf: add btf_has_size() and btf_int() inlines Alan Maguire
2021-01-21  4:11   ` Andrii Nakryiko
2021-01-17 22:16 ` [PATCH v2 bpf-next 2/4] libbpf: make skip_mods_and_typedefs available internally in libbpf Alan Maguire
2021-01-21  4:13   ` Andrii Nakryiko
2021-01-17 22:16 ` Alan Maguire [this message]
2021-01-21  6:56   ` [PATCH v2 bpf-next 3/4] libbpf: BTF dumper support for typed data Andrii Nakryiko
2021-01-21 19:51     ` Andrii Nakryiko
2021-01-22 16:31       ` Alan Maguire
2021-01-22 20:05         ` Andrii Nakryiko
2021-01-17 22:16 ` [PATCH v2 bpf-next 4/4] selftests/bpf: add dump type data tests to btf dump tests Alan Maguire
2021-01-21  7:01   ` Andrii Nakryiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1610921764-7526-4-git-send-email-alan.maguire@oracle.com \
    --to=alan.maguire@oracle.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=john.fastabend@gmail.com \
    --cc=kafai@fb.com \
    --cc=kpsingh@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=morbo@google.com \
    --cc=netdev@vger.kernel.org \
    --cc=shuah@kernel.org \
    --cc=songliubraving@fb.com \
    --cc=yhs@fb.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.