public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Yonghong Song <yhs@fb.com>
To: Alan Maguire <alan.maguire@oracle.com>, <ast@kernel.org>,
	<daniel@iogearbox.net>, <bpf@vger.kernel.org>
Cc: <joe@perches.com>, <linux@rasmusvillemoes.dk>,
	<arnaldo.melo@gmail.com>, <kafai@fb.com>, <songliubraving@fb.com>,
	<andriin@fb.com>, <john.fastabend@gmail.com>,
	<kpsingh@chromium.org>, <linux-kernel@vger.kernel.org>,
	<netdev@vger.kernel.org>
Subject: Re: [PATCH v2 bpf-next 2/7] bpf: move to generic BTF show support, apply it to seq files/strings
Date: Wed, 13 May 2020 16:04:26 -0700	[thread overview]
Message-ID: <2a2c5072-8cd2-610b-db2a-16589df90faf@fb.com> (raw)
In-Reply-To: <1589263005-7887-3-git-send-email-alan.maguire@oracle.com>



On 5/11/20 10:56 PM, Alan Maguire wrote:
> generalize the "seq_show" seq file support in btf.c to support
> a generic show callback of which we support two instances; the
> current seq file show, and a show with snprintf() behaviour which
> instead writes the type data to a supplied string.
> 
> Both classes of show function call btf_type_show() with different
> targets; the seq file or the string to be written.  In the string
> case we need to track additional data - length left in string to write
> and length to return that we would have written (a la snprintf).
> 
> By default show will display type information, field members and
> their types and values etc, and the information is indented
> based upon structure depth.
> 
> Show however supports flags which modify its behaviour:
> 
> BTF_SHOW_COMPACT - suppress newline/indent.
> BTF_SHOW_NONAME - suppress show of type and member names.
> BTF_SHOW_PTR_RAW - do not obfuscate pointer values.
> BTF_SHOW_ZERO - show zeroed values (by default they are not shown).
> 
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
>   include/linux/btf.h |  33 +++
>   kernel/bpf/btf.c    | 759 +++++++++++++++++++++++++++++++++++++++++++++-------
>   2 files changed, 690 insertions(+), 102 deletions(-)
> 
> diff --git a/include/linux/btf.h b/include/linux/btf.h
> index 5c1ea99..d571125 100644
> --- a/include/linux/btf.h
> +++ b/include/linux/btf.h
> @@ -13,6 +13,7 @@
>   struct btf_member;
>   struct btf_type;
>   union bpf_attr;
> +struct btf_show;
>   
>   extern const struct file_operations btf_fops;
>   
> @@ -46,8 +47,40 @@ int btf_get_info_by_fd(const struct btf *btf,
>   const struct btf_type *btf_type_id_size(const struct btf *btf,
>   					u32 *type_id,
>   					u32 *ret_size);
> +
> +/*
> + * Options to control show behaviour.
> + *	- BTF_SHOW_COMPACT: no formatting around type information
> + *	- BTF_SHOW_NONAME: no struct/union member names/types
> + *	- BTF_SHOW_PTR_RAW: show raw (unobfuscated) pointer values;
> + *	  equivalent to %px.
> + *	- BTF_SHOW_ZERO: show zero-valued struct/union members; they
> + *	  are not displayed by default
> + */
> +#define BTF_SHOW_COMPACT	(1ULL << 0)
> +#define BTF_SHOW_NONAME		(1ULL << 1)
> +#define BTF_SHOW_PTR_RAW	(1ULL << 2)
> +#define BTF_SHOW_ZERO		(1ULL << 3)
> +
>   void btf_type_seq_show(const struct btf *btf, u32 type_id, void *obj,
>   		       struct seq_file *m);
> +
> +/*
> + * Copy len bytes of string representation of obj of BTF type_id into buf.
> + *
> + * @btf: struct btf object
> + * @type_id: type id of type obj points to
> + * @obj: pointer to typed data
> + * @buf: buffer to write to
> + * @len: maximum length to write to buf
> + * @flags: show options (see above)
> + *
> + * Return: length that would have been/was copied as per snprintf, or
> + *	   negative error.
> + */
> +int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
> +			   char *buf, int len, u64 flags);
> +
>   int btf_get_fd_by_id(u32 id);
>   u32 btf_id(const struct btf *btf);
>   bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index dcd2331..edf6455 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -281,6 +281,32 @@ static const char *btf_type_str(const struct btf_type *t)
>   	return btf_kind_str[BTF_INFO_KIND(t->info)];
>   }
>   
> +/*
> + * Common data to all BTF show operations. Private show functions can add
> + * their own data to a structure containing a struct btf_show and consult it
> + * in the show callback.  See btf_type_show() below.
> + */
> +struct btf_show {
> +	u64 flags;
> +	void *target;	/* target of show operation (seq file, buffer) */
> +	void (*showfn)(struct btf_show *show, const char *fmt, ...);
> +	const struct btf *btf;
> +	/* below are used during iteration */
> +	struct {
> +		u8 depth;
> +		u8 depth_shown;
> +		u8 depth_check;

I have some difficulties to understand the relationship between
the above three variables. Could you add some comments here?

> +		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[KSYM_NAME_LEN];	/* scratch space for name */
> +		char type_name[KSYM_NAME_LEN];	/* scratch space for type */

KSYM_NAME_LEN is for symbol name, not for type name. But I guess in 
kernel we probably do not have > 128 bytes type name so we should be
okay here.

> +	} state;
> +};
> +
>   struct btf_kind_operations {
>   	s32 (*check_meta)(struct btf_verifier_env *env,
>   			  const struct btf_type *t,
> @@ -297,9 +323,9 @@ struct btf_kind_operations {
>   				  const struct btf_type *member_type);
>   	void (*log_details)(struct btf_verifier_env *env,
>   			    const struct btf_type *t);
> -	void (*seq_show)(const struct btf *btf, const struct btf_type *t,
> +	void (*show)(const struct btf *btf, const struct btf_type *t,
>   			 u32 type_id, void *data, u8 bits_offsets,
> -			 struct seq_file *m);
> +			 struct btf_show *show);
>   };
>   
>   static const struct btf_kind_operations * const kind_ops[NR_BTF_KINDS];
> @@ -676,6 +702,340 @@ bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
>   	return true;
>   }
>   
> +/* Similar to btf_type_skip_modifiers() but does not skip typedefs. */
> +static inline
> +const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, u32 id)
> +{
> +	const struct btf_type *t = btf_type_by_id(btf, id);
> +
> +	while (btf_type_is_modifier(t) &&
> +	       BTF_INFO_KIND(t->info) != BTF_KIND_TYPEDEF) {
> +		id = t->type;
> +		t = btf_type_by_id(btf, t->type);
> +	}
> +
> +	return t;
> +}
> +
> +#define BTF_SHOW_MAX_ITER	10
> +
> +#define BTF_KIND_BIT(kind)	(1ULL << kind)
> +
> +static inline const char *btf_show_type_name(struct btf_show *show,
> +					     const struct btf_type *t)
> +{
> +	const char *array_suffixes = "[][][][][][][][][][]";

Add a comment here saying length BTF_SHOW_MAX_ITER * 2
so later on if somebody changes the BTF_SHOW_MAX_ITER from 10 to 12,
it won't miss here?

> +	const char *array_suffix = &array_suffixes[strlen(array_suffixes)];
> +	const char *ptr_suffixes = "**********";

The same here.

> +	const char *ptr_suffix = &ptr_suffixes[strlen(ptr_suffixes)];
> +	const char *type_name = NULL, *prefix = "", *parens = "";
> +	const struct btf_array *array;
> +	u32 id = show->state.type_id;
> +	bool allow_anon = true;
> +	u64 kinds = 0;
> +	int i;
> +
> +	show->state.type_name[0] = '\0';
> +
> +	/*
> +	 * Start with type_id, as we have have resolved the struct btf_type *
> +	 * via btf_modifier_show() 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 the struct btf_type *
> +	 * in our show->state points at the resolved type of the typedef.
> +	 */
> +	t = btf_type_by_id(show->btf, id);
> +	if (!t)
> +		return show->state.type_name;
> +
> +	/*
> +	 * 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.
> +	 */
> +	for (i = 0; i < BTF_SHOW_MAX_ITER; i++) {
> +
> +		switch (BTF_INFO_KIND(t->info)) {
> +		case BTF_KIND_TYPEDEF:
> +			if (!type_name)
> +				type_name = btf_name_by_offset(show->btf,
> +							       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 = "[";
> +			array = btf_type_array(t);
> +			if (!array)
> +				return show->state.type_name;
> +			if (!t)
> +				return show->state.type_name;
> +			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 = btf_type_skip_qualifiers(show->btf, id);
> +		if (!t)
> +			return show->state.type_name;
> +	}

Do we do pointer tracing here? For example
struct t {
	int *m[5];
}

When trying to access memory, the above code may go through
ptr->array and out of loop when hitting array element type "int"?

> +	/* We may not be able to represent this type; bail to be safe */
> +	if (i == BTF_SHOW_MAX_ITER)
> +		return show->state.type_name;
> +
> +	if (!type_name)
> +		type_name = btf_name_by_offset(show->btf, 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:
> +		allow_anon = false;
> +		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 (!type_name || strlen(type_name) == 0) {
> +		if (allow_anon)
> +			type_name = "";
> +		else
> +			return show->state.type_name;
> +	}
> +
> +	/* Even if we don't want type name info, we want parentheses etc */
> +	if (show->flags & BTF_SHOW_NONAME)
> +		snprintf(show->state.type_name, sizeof(show->state.type_name),
> +			 "%s", parens);
> +	else
> +		snprintf(show->state.type_name, sizeof(show->state.type_name),
> +			 "(%s%s%s%s%s%s)%s",
> +			 prefix,
> +			 strlen(prefix) > 0 && strlen(type_name) > 0 ? " " : "",
> +			 type_name,
> +			 strlen(ptr_suffix) > 0 ? " " : "", ptr_suffix,
> +			 array_suffix, parens);
> +
> +	return show->state.type_name;
> +}
> +
> +static inline const char *btf_show_name(struct btf_show *show)
> +{
> +	const struct btf_type *t = show->state.type;
> +	const struct btf_member *m = show->state.member;
> +	const char *member = NULL;
> +	const char *type = "";
> +
> +	show->state.name[0] = '\0';
> +
> +	if ((!m && !t) || show->state.array_member)
> +		return show->state.name;
> +
> +	if (m)
> +		t = btf_type_skip_qualifiers(show->btf, m->type);
> +
> +	if (t) {
> +		type = btf_show_type_name(show, t);
> +		if (!type)
> +			return show->state.name;
> +	}
> +
> +	if (m && !(show->flags & BTF_SHOW_NONAME)) {
> +		member = btf_name_by_offset(show->btf, m->name_off);
> +		if (member && strlen(member) > 0) {
> +			snprintf(show->state.name, sizeof(show->state.name),
> +				 ".%s = %s", member, type);
> +			return show->state.name;
> +		}
> +	}
> +
> +	snprintf(show->state.name, sizeof(show->state.name), "%s", type);
> +
> +	return show->state.name;
> +}
> +
> +#define btf_show(show, ...)						      \
> +	do {								      \
> +		if (!show->state.depth_check)				      \

As I mentioned above, some comments will be good to understand here.

> +			show->showfn(show, __VA_ARGS__);		      \
> +	} while (0)
> +
> +static inline const char *__btf_show_indent(struct btf_show *show)
> +{
> +	const char *indents = "                                ";
> +	const char *indent = &indents[strlen(indents)];
> +
> +	if ((indent - show->state.depth) >= indents)
> +		return indent - show->state.depth;
> +	return indents;
> +}
> +
> +#define btf_show_indent(show)						       \
> +	((show->flags & BTF_SHOW_COMPACT) ? "" : __btf_show_indent(show))
> +
> +#define btf_show_newline(show)						       \
> +	((show->flags & BTF_SHOW_COMPACT) ? "" : "\n")
> +
> +#define btf_show_delim(show)						       \
> +	(show->state.depth == 0 ? "" :					       \
> +	 ((show->flags & BTF_SHOW_COMPACT) && show->state.type &&	       \
> +	  BTF_INFO_KIND(show->state.type->info) == BTF_KIND_UNION) ? "|" : ",")
> +
> +#define btf_show_type_value(show, fmt, value)				       \
> +	do {								       \
> +		if ((value) != 0 || (show->flags & BTF_SHOW_ZERO) ||	       \
> +		    show->state.depth == 0) {				       \
> +			btf_show(show, "%s%s" fmt "%s%s",		       \
> +				 btf_show_indent(show),			       \
> +				 btf_show_name(show),			       \
> +				 value, btf_show_delim(show),		       \
> +				 btf_show_newline(show));		       \
> +			if (show->state.depth > show->state.depth_shown)       \
> +				show->state.depth_shown = show->state.depth;   \
> +		}							       \
> +	} while (0)
> +
> +#define btf_show_type_values(show, fmt, ...)				       \
> +	do {								       \
> +		btf_show(show, "%s%s" fmt "%s%s", btf_show_indent(show),       \
> +			 btf_show_name(show),				       \
> +			 __VA_ARGS__, btf_show_delim(show),		       \
> +			 btf_show_newline(show));			       \
> +		if (show->state.depth > show->state.depth_shown)	       \
> +			show->state.depth_shown = show->state.depth;	       \
> +	} while (0)
> +
[...]
>   
>   static int btf_array_check_member(struct btf_verifier_env *env,
> @@ -2104,28 +2489,87 @@ static void btf_array_log(struct btf_verifier_env *env,
>   			 array->type, array->index_type, array->nelems);
>   }
>   
> -static void btf_array_seq_show(const struct btf *btf, const struct btf_type *t,
> -			       u32 type_id, void *data, u8 bits_offset,
> -			       struct seq_file *m)
> +static void __btf_array_show(const struct btf *btf, const struct btf_type *t,
> +			     u32 type_id, void *data, u8 bits_offset,
> +			     struct btf_show *show)
>   {
>   	const struct btf_array *array = btf_type_array(t);
>   	const struct btf_kind_operations *elem_ops;
>   	const struct btf_type *elem_type;
> -	u32 i, elem_size, elem_type_id;
> +	u32 i, elem_size = 0, elem_type_id;
> +	u16 encoding = 0;
>   
>   	elem_type_id = array->type;
> -	elem_type = btf_type_id_size(btf, &elem_type_id, &elem_size);
> +	elem_type = btf_type_skip_modifiers(btf, elem_type_id, NULL);
> +	if (elem_type && btf_type_has_size(elem_type))
> +		elem_size = elem_type->size;
> +
> +	if (elem_type && btf_type_is_int(elem_type)) {
> +		u32 int_type = btf_type_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;

Some char array may be printable and some may not be printable,
how did you differentiate this?

> +	}
> +
> +	btf_show_start_array_type(show, t, type_id, encoding);
> +
> +	if (!elem_type)
> +		goto out;
>   	elem_ops = btf_type_ops(elem_type);
> -	seq_puts(m, "[");
> +
>   	for (i = 0; i < array->nelems; i++) {
> -		if (i)
> -			seq_puts(m, ",");
>   
> -		elem_ops->seq_show(btf, elem_type, elem_type_id, data,
> -				   bits_offset, m);
> +		btf_show_start_array_member(show);
> +
> +		elem_ops->show(btf, elem_type, elem_type_id, data,
> +			       bits_offset, show);
>   		data += elem_size;
> +
> +		btf_show_end_array_member(show);
> +
> +		if (show->state.array_terminated)
> +			break;
>   	}
> -	seq_puts(m, "]");
> +out:
> +	btf_show_end_array_type(show);
> +}
> +
[...]

  reply	other threads:[~2020-05-13 23:04 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-05-12  5:56 [PATCH v2 bpf-next 0/7] bpf, printk: add BTF-based type printing Alan Maguire
2020-05-12  5:56 ` [PATCH v2 bpf-next 1/7] bpf: provide function to get vmlinux BTF information Alan Maguire
2020-05-12  5:56 ` [PATCH v2 bpf-next 2/7] bpf: move to generic BTF show support, apply it to seq files/strings Alan Maguire
2020-05-13 23:04   ` Yonghong Song [this message]
2020-05-18  9:46     ` Alan Maguire
2020-05-19  6:21       ` Yonghong Song
2020-05-12  5:56 ` [PATCH v2 bpf-next 3/7] checkpatch: add new BTF pointer format specifier Alan Maguire
2020-05-12  5:56 ` [PATCH v2 bpf-next 4/7] printk: add type-printing %pT format specifier which uses BTF Alan Maguire
2020-05-13 23:05   ` Joe Perches
2020-05-13 23:07     ` Alexei Starovoitov
2020-05-13 23:22       ` Joe Perches
2020-05-14 23:43         ` Joe Perches
2020-05-15  0:09           ` Alexei Starovoitov
2020-05-15  0:21             ` Joe Perches
2020-05-14  0:45   ` Yonghong Song
2020-05-14 22:37     ` Alan Maguire
2020-05-15  0:39       ` Yonghong Song
2020-05-12  5:56 ` [PATCH v2 bpf-next 5/7] printk: extend test_printf to test %pT BTF-based format specifier Alan Maguire
2020-05-12  5:56 ` [PATCH v2 bpf-next 6/7] bpf: add support for %pT format specifier for bpf_trace_printk() helper Alan Maguire
2020-05-14  0:53   ` Yonghong Song
2020-05-18  9:10     ` Alan Maguire
2020-05-18 14:47       ` Yonghong Song
2020-05-12  5:56 ` [PATCH v2 bpf-next 7/7] bpf: add tests for %pT format specifier Alan Maguire
2020-05-15  0:21   ` Andrii Nakryiko
2020-05-13 22:24 ` [PATCH v2 bpf-next 0/7] bpf, printk: add BTF-based type printing Alexei Starovoitov
2020-05-13 22:48   ` Joe Perches
2020-05-13 22:50     ` Alexei Starovoitov
2020-05-13 23:23       ` Joe Perches
2020-05-14 17:46   ` Alan Maguire
2020-05-15 18:59   ` Yonghong Song

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=2a2c5072-8cd2-610b-db2a-16589df90faf@fb.com \
    --to=yhs@fb.com \
    --cc=alan.maguire@oracle.com \
    --cc=andriin@fb.com \
    --cc=arnaldo.melo@gmail.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=joe@perches.com \
    --cc=john.fastabend@gmail.com \
    --cc=kafai@fb.com \
    --cc=kpsingh@chromium.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@rasmusvillemoes.dk \
    --cc=netdev@vger.kernel.org \
    --cc=songliubraving@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox