diff mbox series

[v2,bpf-next,4/7] printk: add type-printing %pT format specifier which uses BTF

Message ID 1589263005-7887-5-git-send-email-alan.maguire@oracle.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series bpf, printk: add BTF-based type printing | expand

Commit Message

Alan Maguire May 12, 2020, 5:56 a.m. UTC
printk supports multiple pointer object type specifiers (printing
netdev features etc).  Extend this support using BTF to cover
arbitrary types.  "%pT" specifies the typed format, and the pointer
argument is a "struct btf_ptr *" where struct btf_ptr is as follows:

struct btf_ptr {
	void *ptr;
	const char *type;
	u32 id;
};

Either the "type" string ("struct sk_buff") or the BTF "id" can be
used to identify the type to use in displaying the associated "ptr"
value.  A convenience function to create and point at the struct
is provided:

	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));

When invoked, BTF information is used to traverse the sk_buff *
and display it.  Support is present for structs, unions, enums,
typedefs and core types (though in the latter case there's not
much value in using this feature of course).

Default output is indented, but compact output can be specified
via the 'c' option.  Type names/member values can be suppressed
using the 'N' option.  Zero values are not displayed by default
but can be using the '0' option.  Pointer values are obfuscated
unless the 'x' option is specified.  As an example:

  struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
  pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));

...gives us:

(struct sk_buff){
 .transport_header = (__u16)65535,
 .mac_header = (__u16)65535,
 .end = (sk_buff_data_t)192,
 .head = (unsigned char *)000000006b71155a,
 .data = (unsigned char *)000000006b71155a,
 .truesize = (unsigned int)768,
 .users = (refcount_t){
  .refs = (atomic_t){
   .counter = (int)1,
  },
 },
 .extensions = (struct skb_ext *)00000000f486a130,
}

printk output is truncated at 1024 bytes.  For cases where overflow
is likely, the compact/no type names display modes may be used.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 Documentation/core-api/printk-formats.rst |  15 ++++
 include/linux/btf.h                       |   3 +-
 include/linux/printk.h                    |  16 +++++
 lib/Kconfig                               |  16 +++++
 lib/vsprintf.c                            | 113 ++++++++++++++++++++++++++++++
 5 files changed, 162 insertions(+), 1 deletion(-)

Comments

Joe Perches May 13, 2020, 11:05 p.m. UTC | #1
On Tue, 2020-05-12 at 06:56 +0100, Alan Maguire wrote:
> printk supports multiple pointer object type specifiers (printing
> netdev features etc).  Extend this support using BTF to cover
> arbitrary types.  "%pT" specifies the typed format, and the pointer
> argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> 
> struct btf_ptr {
> 	void *ptr;
> 	const char *type;
> 	u32 id;
> };
> 
> Either the "type" string ("struct sk_buff") or the BTF "id" can be
> used to identify the type to use in displaying the associated "ptr"
> value.  A convenience function to create and point at the struct
> is provided:
> 
> 	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> 
> When invoked, BTF information is used to traverse the sk_buff *
> and display it.  Support is present for structs, unions, enums,
> typedefs and core types (though in the latter case there's not
> much value in using this feature of course).
> 
> Default output is indented, but compact output can be specified
> via the 'c' option.  Type names/member values can be suppressed
> using the 'N' option.  Zero values are not displayed by default
> but can be using the '0' option.  Pointer values are obfuscated
> unless the 'x' option is specified.  As an example:
> 
>   struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
>   pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> 
> ...gives us:
> 
> (struct sk_buff){
>  .transport_header = (__u16)65535,
> 	 .mac_header = (__u16)65535,
>  .end = (sk_buff_data_t)192,
>  .head = (unsigned char *)000000006b71155a,
>  .data = (unsigned char *)000000006b71155a,
>  .truesize = (unsigned int)768,
>  .users = (refcount_t){
>   .refs = (atomic_t){
>    .counter = (int)1,

Given

  #define BTF_INT_ENCODING(VAL)   (((VAL) & 0x0f000000) >> 24)

Maybe

  #define BTF_INT_SIGNED  (1 << 0)
  #define BTF_INT_CHAR    (1 << 1)
  #define BTF_INT_BOOL    (1 << 2)

could be extended to include

  #define BTF_INT_HEX     (1 << 3)

So hex values can be appropriately pretty-printed.
Alexei Starovoitov May 13, 2020, 11:07 p.m. UTC | #2
On Wed, May 13, 2020 at 4:05 PM Joe Perches <joe@perches.com> wrote:
>
> On Tue, 2020-05-12 at 06:56 +0100, Alan Maguire wrote:
> > printk supports multiple pointer object type specifiers (printing
> > netdev features etc).  Extend this support using BTF to cover
> > arbitrary types.  "%pT" specifies the typed format, and the pointer
> > argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> >
> > struct btf_ptr {
> >       void *ptr;
> >       const char *type;
> >       u32 id;
> > };
> >
> > Either the "type" string ("struct sk_buff") or the BTF "id" can be
> > used to identify the type to use in displaying the associated "ptr"
> > value.  A convenience function to create and point at the struct
> > is provided:
> >
> >       printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> >
> > When invoked, BTF information is used to traverse the sk_buff *
> > and display it.  Support is present for structs, unions, enums,
> > typedefs and core types (though in the latter case there's not
> > much value in using this feature of course).
> >
> > Default output is indented, but compact output can be specified
> > via the 'c' option.  Type names/member values can be suppressed
> > using the 'N' option.  Zero values are not displayed by default
> > but can be using the '0' option.  Pointer values are obfuscated
> > unless the 'x' option is specified.  As an example:
> >
> >   struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
> >   pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> >
> > ...gives us:
> >
> > (struct sk_buff){
> >  .transport_header = (__u16)65535,
> >        .mac_header = (__u16)65535,
> >  .end = (sk_buff_data_t)192,
> >  .head = (unsigned char *)000000006b71155a,
> >  .data = (unsigned char *)000000006b71155a,
> >  .truesize = (unsigned int)768,
> >  .users = (refcount_t){
> >   .refs = (atomic_t){
> >    .counter = (int)1,
>
> Given
>
>   #define BTF_INT_ENCODING(VAL)   (((VAL) & 0x0f000000) >> 24)
>
> Maybe
>
>   #define BTF_INT_SIGNED  (1 << 0)
>   #define BTF_INT_CHAR    (1 << 1)
>   #define BTF_INT_BOOL    (1 << 2)
>
> could be extended to include
>
>   #define BTF_INT_HEX     (1 << 3)
>
> So hex values can be appropriately pretty-printed.

Nack to that.
Joe Perches May 13, 2020, 11:22 p.m. UTC | #3
On Wed, 2020-05-13 at 16:07 -0700, Alexei Starovoitov wrote:
> On Wed, May 13, 2020 at 4:05 PM Joe Perches <joe@perches.com> wrote:
> > On Tue, 2020-05-12 at 06:56 +0100, Alan Maguire wrote:
> > > printk supports multiple pointer object type specifiers (printing
> > > netdev features etc).  Extend this support using BTF to cover
> > > arbitrary types.  "%pT" specifies the typed format, and the pointer
> > > argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> > > 
> > > struct btf_ptr {
> > >       void *ptr;
> > >       const char *type;
> > >       u32 id;
> > > };
> > > 
> > > Either the "type" string ("struct sk_buff") or the BTF "id" can be
> > > used to identify the type to use in displaying the associated "ptr"
> > > value.  A convenience function to create and point at the struct
> > > is provided:
> > > 
> > >       printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > > 
> > > When invoked, BTF information is used to traverse the sk_buff *
> > > and display it.  Support is present for structs, unions, enums,
> > > typedefs and core types (though in the latter case there's not
> > > much value in using this feature of course).
> > > 
> > > Default output is indented, but compact output can be specified
> > > via the 'c' option.  Type names/member values can be suppressed
> > > using the 'N' option.  Zero values are not displayed by default
> > > but can be using the '0' option.  Pointer values are obfuscated
> > > unless the 'x' option is specified.  As an example:
> > > 
> > >   struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
> > >   pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > > 
> > > ...gives us:
> > > 
> > > (struct sk_buff){
> > >  .transport_header = (__u16)65535,
> > >        .mac_header = (__u16)65535,
> > >  .end = (sk_buff_data_t)192,
> > >  .head = (unsigned char *)000000006b71155a,
> > >  .data = (unsigned char *)000000006b71155a,
> > >  .truesize = (unsigned int)768,
> > >  .users = (refcount_t){
> > >   .refs = (atomic_t){
> > >    .counter = (int)1,
> > 
> > Given
> > 
> >   #define BTF_INT_ENCODING(VAL)   (((VAL) & 0x0f000000) >> 24)
> > 
> > Maybe
> > 
> >   #define BTF_INT_SIGNED  (1 << 0)
> >   #define BTF_INT_CHAR    (1 << 1)
> >   #define BTF_INT_BOOL    (1 << 2)
> > 
> > could be extended to include
> > 
> >   #define BTF_INT_HEX     (1 << 3)
> > 
> > So hex values can be appropriately pretty-printed.
> 
> Nack to that.

why?
Yonghong Song May 14, 2020, 12:45 a.m. UTC | #4
On 5/11/20 10:56 PM, Alan Maguire wrote:
> printk supports multiple pointer object type specifiers (printing
> netdev features etc).  Extend this support using BTF to cover
> arbitrary types.  "%pT" specifies the typed format, and the pointer
> argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> 
> struct btf_ptr {
> 	void *ptr;
> 	const char *type;
> 	u32 id;
> };
> 
> Either the "type" string ("struct sk_buff") or the BTF "id" can be
> used to identify the type to use in displaying the associated "ptr"
> value.  A convenience function to create and point at the struct
> is provided:
> 
> 	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> 
> When invoked, BTF information is used to traverse the sk_buff *
> and display it.  Support is present for structs, unions, enums,
> typedefs and core types (though in the latter case there's not
> much value in using this feature of course).
> 
> Default output is indented, but compact output can be specified
> via the 'c' option.  Type names/member values can be suppressed
> using the 'N' option.  Zero values are not displayed by default
> but can be using the '0' option.  Pointer values are obfuscated
> unless the 'x' option is specified.  As an example:
> 
>    struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
>    pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> 
> ...gives us:
> 
> (struct sk_buff){
>   .transport_header = (__u16)65535,
>   .mac_header = (__u16)65535,
>   .end = (sk_buff_data_t)192,
>   .head = (unsigned char *)000000006b71155a,
>   .data = (unsigned char *)000000006b71155a,
>   .truesize = (unsigned int)768,
>   .users = (refcount_t){
>    .refs = (atomic_t){
>     .counter = (int)1,
>    },
>   },
>   .extensions = (struct skb_ext *)00000000f486a130,
> }
> 
> printk output is truncated at 1024 bytes.  For cases where overflow
> is likely, the compact/no type names display modes may be used.
> 
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
>   Documentation/core-api/printk-formats.rst |  15 ++++
>   include/linux/btf.h                       |   3 +-
>   include/linux/printk.h                    |  16 +++++
>   lib/Kconfig                               |  16 +++++
>   lib/vsprintf.c                            | 113 ++++++++++++++++++++++++++++++
>   5 files changed, 162 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
> index 8ebe46b1..5c66097 100644
> --- a/Documentation/core-api/printk-formats.rst
> +++ b/Documentation/core-api/printk-formats.rst
> @@ -545,6 +545,21 @@ For printing netdev_features_t.
>   
>   Passed by reference.
>   
> +BTF-based printing of pointer data
> +----------------------------------
> +If '%pT' is specified, use the struct btf_ptr * along with kernel vmlinux
> +BPF Type Format (BTF) to show the typed data.  For example, specifying
> +
> +	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct_sk_buff));
> +
> +will utilize BTF information to traverse the struct sk_buff * and display it.
> +
> +Supported modifers are
> + 'c' compact output (no indentation, newlines etc)
> + 'N' do not show type names
> + 'x' show raw pointers (no obfuscation)
> + '0' show zero-valued data (it is not shown by default)
> +
>   Thanks
>   ======
>   
> diff --git a/include/linux/btf.h b/include/linux/btf.h
> index d571125..7b585ab 100644
> --- a/include/linux/btf.h
> +++ b/include/linux/btf.h
> @@ -169,10 +169,11 @@ static inline const struct btf_member *btf_type_member(const struct btf_type *t)
>   	return (const struct btf_member *)(t + 1);
>   }
>   
> +struct btf *btf_parse_vmlinux(void);
> +
>   #ifdef CONFIG_BPF_SYSCALL
>   const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
>   const char *btf_name_by_offset(const struct btf *btf, u32 offset);
> -struct btf *btf_parse_vmlinux(void);
>   struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog);
>   #else
>   static inline const struct btf_type *btf_type_by_id(const struct btf *btf,
> diff --git a/include/linux/printk.h b/include/linux/printk.h
> index fcde0772..3c3ea53 100644
> --- a/include/linux/printk.h
> +++ b/include/linux/printk.h
> @@ -528,4 +528,20 @@ static inline void print_hex_dump_debug(const char *prefix_str, int prefix_type,
>   #define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
>   	print_hex_dump_debug(prefix_str, prefix_type, 16, 1, buf, len, true)
>   
> +/**
> + * struct btf_ptr is used for %pT (typed pointer) display; the
> + * additional type string/BTF id are used to render the pointer
> + * data as the appropriate type.
> + */
> +struct btf_ptr {
> +	void *ptr;
> +	const char *type;
> +	u32 id;
> +};
> +
> +#define	BTF_PTR_TYPE(ptrval, typeval) \
> +	(&((struct btf_ptr){.ptr = ptrval, .type = #typeval}))
> +
> +#define BTF_PTR_ID(ptrval, idval) \
> +	(&((struct btf_ptr){.ptr = ptrval, .id = idval}))
>   #endif
[...]
> diff --git a/lib/vsprintf.c b/lib/vsprintf.c
> index 7c488a1..f9276f8 100644
> --- a/lib/vsprintf.c
> +++ b/lib/vsprintf.c
> @@ -43,6 +43,7 @@
>   #ifdef CONFIG_BLOCK
>   #include <linux/blkdev.h>
>   #endif
> +#include <linux/btf.h>
>   
>   #include "../mm/internal.h"	/* For the trace_print_flags arrays */
>   
> @@ -2059,6 +2060,103 @@ char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode,
>   	return widen_string(buf, buf - buf_start, end, spec);
>   }
>   
> +#if IS_ENABLED(CONFIG_BTF_PRINTF)
> +#define btf_modifier_flag(c)	(c == 'c' ? BTF_SHOW_COMPACT :	\
> +				 c == 'N' ? BTF_SHOW_NONAME :	\
> +				 c == 'x' ? BTF_SHOW_PTR_RAW :	\
> +				 c == '0' ? BTF_SHOW_ZERO : 0)
> +
> +static noinline_for_stack
> +char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
> +		 const char *fmt)
> +{
> +	struct btf_ptr *bp = (struct btf_ptr *)ptr;
> +	u8 btf_kind = BTF_KIND_TYPEDEF;
> +	const struct btf_type *t;
> +	const struct btf *btf;
> +	char *buf_start = buf;
> +	const char *btf_type;
> +	u64 flags = 0, mod;
> +	s32 btf_id;
> +
> +	if (check_pointer(&buf, end, ptr, spec))
> +		return buf;
> +
> +	if (check_pointer(&buf, end, bp->ptr, spec))
> +		return buf;
> +
> +	while (isalnum(*fmt)) {
> +		mod = btf_modifier_flag(*fmt);
> +		if (!mod)
> +			break;
> +		flags |= mod;
> +		fmt++;
> +	}
> +
> +	btf = bpf_get_btf_vmlinux();
> +	if (IS_ERR_OR_NULL(btf))
> +		return ptr_to_id(buf, end, bp->ptr, spec);
> +
> +	if (bp->type != NULL) {
> +		btf_type = bp->type;
> +
> +		if (strncmp(bp->type, "struct ", strlen("struct ")) == 0) {
> +			btf_kind = BTF_KIND_STRUCT;
> +			btf_type += strlen("struct ");
> +		} else if (strncmp(btf_type, "union ", strlen("union ")) == 0) {
> +			btf_kind = BTF_KIND_UNION;
> +			btf_type += strlen("union ");
> +		} else if (strncmp(btf_type, "enum ", strlen("enum ")) == 0) {
> +			btf_kind = BTF_KIND_ENUM;
> +			btf_type += strlen("enum ");
> +		}

I think typedef should be supported here.
In kernel, we have some structure directly defined as typedef's.
A lot of internal int types also typedefs, like u32, atomic_t,
possible_net_t, etc.

A type name without prefix "struct", "union", "enum" can be
treated as a typedef first.

If the type name is not a typedef, it is then compared to a limited
number of C basic int types like "char", "unsigned char", "short",
"unsigned short", ...

> +
> +		if (strlen(btf_type) == 0)
> +			return ptr_to_id(buf, end, bp->ptr, spec);
> +
> +		/*
> +		 * Assume type specified is a typedef as there's not much
> +		 * benefit in specifying int types other than wasting time
> +		 * on BTF lookups; we optimize for the most useful path.
> +		 *
> +		 * Fall back to BTF_KIND_INT if this fails.
> +		 */
> +		btf_id = btf_find_by_name_kind(btf, btf_type, btf_kind);
> +		if (btf_id < 0)
> +			btf_id = btf_find_by_name_kind(btf, btf_type,
> +						       BTF_KIND_INT);
> +	} else if (bp->id > 0)
> +		btf_id = bp->id;
> +	else
> +		return ptr_to_id(buf, end, bp->ptr, spec);
> +
> +	if (btf_id > 0)
> +		t = btf_type_by_id(btf, btf_id);
> +	if (btf_id <= 0 || !t)
> +		return ptr_to_id(buf, end, bp->ptr, spec);
> +
> +	buf += btf_type_snprintf_show(btf, btf_id, bp->ptr, buf,
> +				      end - buf_start, flags);
> +
> +	return widen_string(buf, buf - buf_start, end, spec);
> +}
> +#else
> +static noinline_for_stack
> +char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
> +	const char *fmt)
> +{
> +	struct btf_ptr *bp = (struct btf_ptr *)ptr;
> +
> +	if (check_pointer(&buf, end, ptr, spec))
> +		return buf;
> +
> +	if (check_pointer(&buf, end, bp->ptr, spec))
> +		return buf;
> +
> +	return ptr_to_id(buf, end, bp->ptr, spec);
> +}
> +#endif /* IS_ENABLED(CONFIG_BTF_PRINTF) */
> +
>   /*
>    * Show a '%p' thing.  A kernel extension is that the '%p' is followed
>    * by an extra set of alphanumeric characters that are extended format
> @@ -2169,6 +2267,19 @@ char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode,
>    *		P node name, including a possible unit address
>    * - 'x' For printing the address. Equivalent to "%lx".
>    *
> + * - 'T[cNx0]' For printing struct btf_ptr * data using BPF Type Format (BTF).
> + *
> + *			Optional arguments are
> + *			c		compact (no indentation/newlines)
> + *			N		do not print type and member names
> + *			x		do not obfuscate pointers
> + *			0		show 0-valued data
> + *
> + *    BPF_PTR_TYPE(ptr, type) can be used to place pointer and type string
> + *    in the "struct btf_ptr *" expected; for example:
> + *
> + *	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> + *
>    * ** When making changes please also update:
>    *	Documentation/core-api/printk-formats.rst
>    *
> @@ -2251,6 +2362,8 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
>   		if (!IS_ERR(ptr))
>   			break;
>   		return err_ptr(buf, end, ptr, spec);
> +	case 'T':
> +		return btf_string(buf, end, ptr, spec, fmt + 1);
>   	}
>   
>   	/* default is to _not_ leak addresses, hash before printing */
>
Alan Maguire May 14, 2020, 10:37 p.m. UTC | #5
On Wed, 13 May 2020, Yonghong Song wrote:

> 
> 
> On 5/11/20 10:56 PM, Alan Maguire wrote:
> > printk supports multiple pointer object type specifiers (printing
> > netdev features etc).  Extend this support using BTF to cover
> > arbitrary types.  "%pT" specifies the typed format, and the pointer
> > argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> > 
> > struct btf_ptr {
> >  void *ptr;
> >  const char *type;
> >  u32 id;
> > };
> > 
> > Either the "type" string ("struct sk_buff") or the BTF "id" can be
> > used to identify the type to use in displaying the associated "ptr"
> > value.  A convenience function to create and point at the struct
> > is provided:
> > 
> >  printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > 
> > When invoked, BTF information is used to traverse the sk_buff *
> > and display it.  Support is present for structs, unions, enums,
> > typedefs and core types (though in the latter case there's not
> > much value in using this feature of course).
> > 
> > Default output is indented, but compact output can be specified
> > via the 'c' option.  Type names/member values can be suppressed
> > using the 'N' option.  Zero values are not displayed by default
> > but can be using the '0' option.  Pointer values are obfuscated
> > unless the 'x' option is specified.  As an example:
> > 
> >    struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
> >    pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > 
> > ...gives us:
> > 
> > (struct sk_buff){
> >   .transport_header = (__u16)65535,
> >   .mac_header = (__u16)65535,
> >   .end = (sk_buff_data_t)192,
> >   .head = (unsigned char *)000000006b71155a,
> >   .data = (unsigned char *)000000006b71155a,
> >   .truesize = (unsigned int)768,
> >   .users = (refcount_t){
> >    .refs = (atomic_t){
> >    .counter = (int)1,
> >   },
> >   },
> >   .extensions = (struct skb_ext *)00000000f486a130,
> > }
> > 
> > printk output is truncated at 1024 bytes.  For cases where overflow
> > is likely, the compact/no type names display modes may be used.
> > 
> > Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> > ---
> >   Documentation/core-api/printk-formats.rst |  15 ++++
> >   include/linux/btf.h                       |   3 +-
> >   include/linux/printk.h                    |  16 +++++
> >   lib/Kconfig                               |  16 +++++
> >   lib/vsprintf.c                            | 113
> >   ++++++++++++++++++++++++++++++
> >   5 files changed, 162 insertions(+), 1 deletion(-)
> > 
> > diff --git a/Documentation/core-api/printk-formats.rst
> > b/Documentation/core-api/printk-formats.rst
> > index 8ebe46b1..5c66097 100644
> > --- a/Documentation/core-api/printk-formats.rst
> > +++ b/Documentation/core-api/printk-formats.rst
> > @@ -545,6 +545,21 @@ For printing netdev_features_t.
> >   
> >   Passed by reference.
> >   
> > +BTF-based printing of pointer data
> > +----------------------------------
> > +If '%pT' is specified, use the struct btf_ptr * along with kernel vmlinux
> > +BPF Type Format (BTF) to show the typed data.  For example, specifying
> > +
> > +	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct_sk_buff));
> > +
> > +will utilize BTF information to traverse the struct sk_buff * and display
> > it.
> > +
> > +Supported modifers are
> > + 'c' compact output (no indentation, newlines etc)
> > + 'N' do not show type names
> > + 'x' show raw pointers (no obfuscation)
> > + '0' show zero-valued data (it is not shown by default)
> > +
> >   Thanks
> >   ======
> >   
> > diff --git a/include/linux/btf.h b/include/linux/btf.h
> > index d571125..7b585ab 100644
> > --- a/include/linux/btf.h
> > +++ b/include/linux/btf.h
> > @@ -169,10 +169,11 @@ static inline const struct btf_member
> > *btf_type_member(const struct btf_type *t)
> >   	return (const struct btf_member *)(t + 1);
> >   }
> >   
> > +struct btf *btf_parse_vmlinux(void);
> > +
> >   #ifdef CONFIG_BPF_SYSCALL
> >   const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
> >   const char *btf_name_by_offset(const struct btf *btf, u32 offset);
> > -struct btf *btf_parse_vmlinux(void);
> >   struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog);
> >   #else
> >   static inline const struct btf_type *btf_type_by_id(const struct btf *btf,
> > diff --git a/include/linux/printk.h b/include/linux/printk.h
> > index fcde0772..3c3ea53 100644
> > --- a/include/linux/printk.h
> > +++ b/include/linux/printk.h
> > @@ -528,4 +528,20 @@ static inline void print_hex_dump_debug(const char
> > *prefix_str, int prefix_type,
> >   #define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
> >    print_hex_dump_debug(prefix_str, prefix_type, 16, 1, buf, len, true)
> >   +/**
> > + * struct btf_ptr is used for %pT (typed pointer) display; the
> > + * additional type string/BTF id are used to render the pointer
> > + * data as the appropriate type.
> > + */
> > +struct btf_ptr {
> > +	void *ptr;
> > +	const char *type;
> > +	u32 id;
> > +};
> > +
> > +#define	BTF_PTR_TYPE(ptrval, typeval) \
> > +	(&((struct btf_ptr){.ptr = ptrval, .type = #typeval}))
> > +
> > +#define BTF_PTR_ID(ptrval, idval) \
> > +	(&((struct btf_ptr){.ptr = ptrval, .id = idval}))
> >   #endif
> [...]
> > diff --git a/lib/vsprintf.c b/lib/vsprintf.c
> > index 7c488a1..f9276f8 100644
> > --- a/lib/vsprintf.c
> > +++ b/lib/vsprintf.c
> > @@ -43,6 +43,7 @@
> >   #ifdef CONFIG_BLOCK
> >   #include <linux/blkdev.h>
> >   #endif
> > +#include <linux/btf.h>
> >   
> >   #include "../mm/internal.h"	/* For the trace_print_flags arrays */
> >   
> > @@ -2059,6 +2060,103 @@ char *fwnode_string(char *buf, char *end, struct
> > fwnode_handle *fwnode,
> >   	return widen_string(buf, buf - buf_start, end, spec);
> >   }
> >   
> > +#if IS_ENABLED(CONFIG_BTF_PRINTF)
> > +#define btf_modifier_flag(c)	(c == 'c' ? BTF_SHOW_COMPACT :	\
> > +				 c == 'N' ? BTF_SHOW_NONAME :	\
> > +				 c == 'x' ? BTF_SHOW_PTR_RAW :	\
> > +				 c == '0' ? BTF_SHOW_ZERO : 0)
> > +
> > +static noinline_for_stack
> > +char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
> > +		 const char *fmt)
> > +{
> > +	struct btf_ptr *bp = (struct btf_ptr *)ptr;
> > +	u8 btf_kind = BTF_KIND_TYPEDEF;
> > +	const struct btf_type *t;
> > +	const struct btf *btf;
> > +	char *buf_start = buf;
> > +	const char *btf_type;
> > +	u64 flags = 0, mod;
> > +	s32 btf_id;
> > +
> > +	if (check_pointer(&buf, end, ptr, spec))
> > +		return buf;
> > +
> > +	if (check_pointer(&buf, end, bp->ptr, spec))
> > +		return buf;
> > +
> > +	while (isalnum(*fmt)) {
> > +		mod = btf_modifier_flag(*fmt);
> > +		if (!mod)
> > +			break;
> > +		flags |= mod;
> > +		fmt++;
> > +	}
> > +
> > +	btf = bpf_get_btf_vmlinux();
> > +	if (IS_ERR_OR_NULL(btf))
> > +		return ptr_to_id(buf, end, bp->ptr, spec);
> > +
> > +	if (bp->type != NULL) {
> > +		btf_type = bp->type;
> > +
> > +		if (strncmp(bp->type, "struct ", strlen("struct ")) == 0) {
> > +			btf_kind = BTF_KIND_STRUCT;
> > +			btf_type += strlen("struct ");
> > +		} else if (strncmp(btf_type, "union ", strlen("union ")) == 0)
> > {
> > +			btf_kind = BTF_KIND_UNION;
> > +			btf_type += strlen("union ");
> > +		} else if (strncmp(btf_type, "enum ", strlen("enum ")) == 0) {
> > +			btf_kind = BTF_KIND_ENUM;
> > +			btf_type += strlen("enum ");
> > +		}
> 
> I think typedef should be supported here.
> In kernel, we have some structure directly defined as typedef's.
> A lot of internal int types also typedefs, like u32, atomic_t,
> possible_net_t, etc.
> 
> A type name without prefix "struct", "union", "enum" can be
> treated as a typedef first.
> 

That's how the code works today; we start with a typedef assumption.
See the comment below starting "Assume type specified is a typedef";
we initialize btf_kind to be a typedef above; it's only changed
to an BTF_KIND_INT if we find a struct/enum/union prefix or if lookup
using the typedef kind fails. I should probably make this clearer
though (move the comment up maybe?). Thanks for taking a look!

> If the type name is not a typedef, it is then compared to a limited
> number of C basic int types like "char", "unsigned char", "short",
> "unsigned short", ...
> 
> > +
> > +		if (strlen(btf_type) == 0)
> > +			return ptr_to_id(buf, end, bp->ptr, spec);
> > +
> > +		/*
> > +		 * Assume type specified is a typedef as there's not much
> > +		 * benefit in specifying int types other than wasting time
> > +		 * on BTF lookups; we optimize for the most useful path.
> > +		 *
> > +		 * Fall back to BTF_KIND_INT if this fails.
> > +		 */
> > +		btf_id = btf_find_by_name_kind(btf, btf_type, btf_kind);
> > +		if (btf_id < 0)
> > +			btf_id = btf_find_by_name_kind(btf, btf_type,
> > +						       BTF_KIND_INT);
> > +	} else if (bp->id > 0)
> > +		btf_id = bp->id;
> > +	else
> > +		return ptr_to_id(buf, end, bp->ptr, spec);
> > +
> > +	if (btf_id > 0)
> > +		t = btf_type_by_id(btf, btf_id);
> > +	if (btf_id <= 0 || !t)
> > +		return ptr_to_id(buf, end, bp->ptr, spec);
> > +
> > +	buf += btf_type_snprintf_show(btf, btf_id, bp->ptr, buf,
> > +				      end - buf_start, flags);
> > +
> > +	return widen_string(buf, buf - buf_start, end, spec);
> > +}
> > +#else
> > +static noinline_for_stack
> > +char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
> > +	const char *fmt)
> > +{
> > +	struct btf_ptr *bp = (struct btf_ptr *)ptr;
> > +
> > +	if (check_pointer(&buf, end, ptr, spec))
> > +		return buf;
> > +
> > +	if (check_pointer(&buf, end, bp->ptr, spec))
> > +		return buf;
> > +
> > +	return ptr_to_id(buf, end, bp->ptr, spec);
> > +}
> > +#endif /* IS_ENABLED(CONFIG_BTF_PRINTF) */
> > +
> >   /*
> >    * Show a '%p' thing.  A kernel extension is that the '%p' is followed
> >    * by an extra set of alphanumeric characters that are extended format
> > @@ -2169,6 +2267,19 @@ char *fwnode_string(char *buf, char *end, struct
> > fwnode_handle *fwnode,
> >    *		P node name, including a possible unit address
> >    * - 'x' For printing the address. Equivalent to "%lx".
> >    *
> > + * - 'T[cNx0]' For printing struct btf_ptr * data using BPF Type Format
> > (BTF).
> > + *
> > + *			Optional arguments are
> > + *			c		compact (no indentation/newlines)
> > + *			N		do not print type and member names
> > + *			x		do not obfuscate pointers
> > + *			0		show 0-valued data
> > + *
> > + *    BPF_PTR_TYPE(ptr, type) can be used to place pointer and type string
> > + *    in the "struct btf_ptr *" expected; for example:
> > + *
> > + *	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > + *
> >    * ** When making changes please also update:
> >    *	Documentation/core-api/printk-formats.rst
> >    *
> > @@ -2251,6 +2362,8 @@ char *pointer(const char *fmt, char *buf, char *end,
> > void *ptr,
> >     if (!IS_ERR(ptr))
> >     	break;
> >   		return err_ptr(buf, end, ptr, spec);
> > +	case 'T':
> > +		return btf_string(buf, end, ptr, spec, fmt + 1);
> >    }
> >   
> >    /* default is to _not_ leak addresses, hash before printing */
> > 
> 
>
Joe Perches May 14, 2020, 11:43 p.m. UTC | #6
On Wed, 2020-05-13 at 16:22 -0700, Joe Perches wrote:
> On Wed, 2020-05-13 at 16:07 -0700, Alexei Starovoitov wrote:
> > On Wed, May 13, 2020 at 4:05 PM Joe Perches <joe@perches.com> wrote:
> > > On Tue, 2020-05-12 at 06:56 +0100, Alan Maguire wrote:
> > > > printk supports multiple pointer object type specifiers (printing
> > > > netdev features etc).  Extend this support using BTF to cover
> > > > arbitrary types.  "%pT" specifies the typed format, and the pointer
> > > > argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
> > > > 
> > > > struct btf_ptr {
> > > >       void *ptr;
> > > >       const char *type;
> > > >       u32 id;
> > > > };
> > > > 
> > > > Either the "type" string ("struct sk_buff") or the BTF "id" can be
> > > > used to identify the type to use in displaying the associated "ptr"
> > > > value.  A convenience function to create and point at the struct
> > > > is provided:
> > > > 
> > > >       printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > > > 
> > > > When invoked, BTF information is used to traverse the sk_buff *
> > > > and display it.  Support is present for structs, unions, enums,
> > > > typedefs and core types (though in the latter case there's not
> > > > much value in using this feature of course).
> > > > 
> > > > Default output is indented, but compact output can be specified
> > > > via the 'c' option.  Type names/member values can be suppressed
> > > > using the 'N' option.  Zero values are not displayed by default
> > > > but can be using the '0' option.  Pointer values are obfuscated
> > > > unless the 'x' option is specified.  As an example:
> > > > 
> > > >   struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
> > > >   pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
> > > > 
> > > > ...gives us:
> > > > 
> > > > (struct sk_buff){
> > > >  .transport_header = (__u16)65535,
> > > >        .mac_header = (__u16)65535,
> > > >  .end = (sk_buff_data_t)192,
> > > >  .head = (unsigned char *)000000006b71155a,
> > > >  .data = (unsigned char *)000000006b71155a,
> > > >  .truesize = (unsigned int)768,
> > > >  .users = (refcount_t){
> > > >   .refs = (atomic_t){
> > > >    .counter = (int)1,
> > > 
> > > Given
> > > 
> > >   #define BTF_INT_ENCODING(VAL)   (((VAL) & 0x0f000000) >> 24)
> > > 
> > > Maybe
> > > 
> > >   #define BTF_INT_SIGNED  (1 << 0)
> > >   #define BTF_INT_CHAR    (1 << 1)
> > >   #define BTF_INT_BOOL    (1 << 2)
> > > 
> > > could be extended to include
> > > 
> > >   #define BTF_INT_HEX     (1 << 3)
> > > 
> > > So hex values can be appropriately pretty-printed.
> > 
> > Nack to that.
> 
> why?
> 

Tell me what's wrong with the idea.

Here's a possible implementation:
---
 Documentation/bpf/btf.rst      |  5 +++--
 include/uapi/linux/btf.h       |  1 +
 kernel/bpf/btf.c               |  5 ++++-
 tools/bpf/bpftool/btf.c        |  2 ++
 tools/bpf/bpftool/btf_dumper.c | 13 +++++++++++++
 tools/include/uapi/linux/btf.h |  1 +
 6 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/Documentation/bpf/btf.rst b/Documentation/bpf/btf.rst
index 4d565d202ce3..56aaa189e7fb 100644
--- a/Documentation/bpf/btf.rst
+++ b/Documentation/bpf/btf.rst
@@ -139,10 +139,11 @@ The ``BTF_INT_ENCODING`` has the following attributes::
   #define BTF_INT_SIGNED  (1 << 0)
   #define BTF_INT_CHAR    (1 << 1)
   #define BTF_INT_BOOL    (1 << 2)
+  #define BTF_INT_HEX     (1 << 3)
 
 The ``BTF_INT_ENCODING()`` provides extra information: signedness, char, or
-bool, for the int type. The char and bool encoding are mostly useful for
-pretty print. At most one encoding can be specified for the int type.
+bool, for the int type. The char, bool and hex encodings are mostly useful
+for pretty print. At most one encoding can be specified for the int type.
 
 The ``BTF_INT_BITS()`` specifies the number of actual bits held by this int
 type. For example, a 4-bit bitfield encodes ``BTF_INT_BITS()`` equals to 4.
diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
index 5a667107ad2c..36f309209786 100644
--- a/include/uapi/linux/btf.h
+++ b/include/uapi/linux/btf.h
@@ -90,6 +90,7 @@ struct btf_type {
 #define BTF_INT_SIGNED	(1 << 0)
 #define BTF_INT_CHAR	(1 << 1)
 #define BTF_INT_BOOL	(1 << 2)
+#define BTF_INT_HEX	(1 << 3)
 
 /* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
  * The exact number of btf_enum is stored in the vlen (of the
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 58c9af1d4808..90bdc0635321 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -501,6 +501,8 @@ static const char *btf_int_encoding_str(u8 encoding)
 		return "CHAR";
 	else if (encoding == BTF_INT_BOOL)
 		return "BOOL";
+	else if (encoding == BTF_INT_HEX)
+		return "HEX";
 	else
 		return "UNKN";
 }
@@ -1404,7 +1406,8 @@ static s32 btf_int_check_meta(struct btf_verifier_env *env,
 	if (encoding &&
 	    encoding != BTF_INT_SIGNED &&
 	    encoding != BTF_INT_CHAR &&
-	    encoding != BTF_INT_BOOL) {
+	    encoding != BTF_INT_BOOL &&
+	    encoding != BTF_INT_HEX) {
 		btf_verifier_log_type(env, t, "Unsupported encoding");
 		return -ENOTSUPP;
 	}
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 41a1346934a1..44a129c40873 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -59,6 +59,8 @@ static const char *btf_int_enc_str(__u8 encoding)
 		return "CHAR";
 	case BTF_INT_BOOL:
 		return "BOOL";
+	case BTF_INT_HEX:
+		return "HEX";
 	default:
 		return "UNKN";
 	}
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index ede162f83eea..96947ef92565 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -418,6 +418,19 @@ static int btf_dumper_int(const struct btf_type *t, __u8 bit_offset,
 	case BTF_INT_BOOL:
 		jsonw_bool(jw, *(int *)data);
 		break;
+	case BTF_INT_HEX:
+		if (BTF_INT_BITS(*int_type) == 64)
+			jsonw_printf(jw, "%llx", *(long long *)data);
+		else if (BTF_INT_BITS(*int_type) == 32)
+			jsonw_printf(jw, "%x", *(int *)data);
+		else if (BTF_INT_BITS(*int_type) == 16)
+			jsonw_printf(jw, "%hx", *(short *)data);
+		else if (BTF_INT_BITS(*int_type) == 8)
+			jsonw_printf(jw, "%hhx", *(char *)data);
+		else
+			btf_dumper_int_bits(*int_type, bit_offset, data, jw,
+					    is_plain_text);
+		break;
 	default:
 		/* shouldn't happen */
 		return -EINVAL;
diff --git a/tools/include/uapi/linux/btf.h b/tools/include/uapi/linux/btf.h
index 5a667107ad2c..36f309209786 100644
--- a/tools/include/uapi/linux/btf.h
+++ b/tools/include/uapi/linux/btf.h
@@ -90,6 +90,7 @@ struct btf_type {
 #define BTF_INT_SIGNED	(1 << 0)
 #define BTF_INT_CHAR	(1 << 1)
 #define BTF_INT_BOOL	(1 << 2)
+#define BTF_INT_HEX	(1 << 3)
 
 /* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
  * The exact number of btf_enum is stored in the vlen (of the
Alexei Starovoitov May 15, 2020, 12:09 a.m. UTC | #7
On Thu, May 14, 2020 at 04:43:24PM -0700, Joe Perches wrote:
>  The ``BTF_INT_BITS()`` specifies the number of actual bits held by this int
>  type. For example, a 4-bit bitfield encodes ``BTF_INT_BITS()`` equals to 4.
> diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
> index 5a667107ad2c..36f309209786 100644
> --- a/include/uapi/linux/btf.h
> +++ b/include/uapi/linux/btf.h
> @@ -90,6 +90,7 @@ struct btf_type {
>  #define BTF_INT_SIGNED	(1 << 0)
>  #define BTF_INT_CHAR	(1 << 1)
>  #define BTF_INT_BOOL	(1 << 2)
> +#define BTF_INT_HEX	(1 << 3)

Nack.
Hex is not a type.
Joe Perches May 15, 2020, 12:21 a.m. UTC | #8
On Thu, 2020-05-14 at 17:09 -0700, Alexei Starovoitov wrote:
> On Thu, May 14, 2020 at 04:43:24PM -0700, Joe Perches wrote:
> >  The ``BTF_INT_BITS()`` specifies the number of actual bits held by this int
> >  type. For example, a 4-bit bitfield encodes ``BTF_INT_BITS()`` equals to 4.
> > diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
> > index 5a667107ad2c..36f309209786 100644
> > --- a/include/uapi/linux/btf.h
> > +++ b/include/uapi/linux/btf.h
> > @@ -90,6 +90,7 @@ struct btf_type {
> >  #define BTF_INT_SIGNED	(1 << 0)
> >  #define BTF_INT_CHAR	(1 << 1)
> >  #define BTF_INT_BOOL	(1 << 2)
> > +#define BTF_INT_HEX	(1 << 3)
> 
> Nack.
> Hex is not a type.

It is a pretty print output format.
Yonghong Song May 15, 2020, 12:39 a.m. UTC | #9
On 5/14/20 3:37 PM, Alan Maguire wrote:
> 
> 
> On Wed, 13 May 2020, Yonghong Song wrote:
> 
>>
>>
>> On 5/11/20 10:56 PM, Alan Maguire wrote:
>>> printk supports multiple pointer object type specifiers (printing
>>> netdev features etc).  Extend this support using BTF to cover
>>> arbitrary types.  "%pT" specifies the typed format, and the pointer
>>> argument is a "struct btf_ptr *" where struct btf_ptr is as follows:
>>>
>>> struct btf_ptr {
>>>   void *ptr;
>>>   const char *type;
>>>   u32 id;
>>> };
>>>
>>> Either the "type" string ("struct sk_buff") or the BTF "id" can be
>>> used to identify the type to use in displaying the associated "ptr"
>>> value.  A convenience function to create and point at the struct
>>> is provided:
>>>
>>>   printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
>>>
>>> When invoked, BTF information is used to traverse the sk_buff *
>>> and display it.  Support is present for structs, unions, enums,
>>> typedefs and core types (though in the latter case there's not
>>> much value in using this feature of course).
>>>
>>> Default output is indented, but compact output can be specified
>>> via the 'c' option.  Type names/member values can be suppressed
>>> using the 'N' option.  Zero values are not displayed by default
>>> but can be using the '0' option.  Pointer values are obfuscated
>>> unless the 'x' option is specified.  As an example:
>>>
>>>     struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
>>>     pr_info("%pT", BTF_PTR_TYPE(skb, struct sk_buff));
>>>
>>> ...gives us:
>>>
>>> (struct sk_buff){
>>>    .transport_header = (__u16)65535,
>>>    .mac_header = (__u16)65535,
>>>    .end = (sk_buff_data_t)192,
>>>    .head = (unsigned char *)000000006b71155a,
>>>    .data = (unsigned char *)000000006b71155a,
>>>    .truesize = (unsigned int)768,
>>>    .users = (refcount_t){
>>>     .refs = (atomic_t){
>>>     .counter = (int)1,
>>>    },
>>>    },
>>>    .extensions = (struct skb_ext *)00000000f486a130,
>>> }
>>>
>>> printk output is truncated at 1024 bytes.  For cases where overflow
>>> is likely, the compact/no type names display modes may be used.
>>>
>>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>>> ---
>>>    Documentation/core-api/printk-formats.rst |  15 ++++
>>>    include/linux/btf.h                       |   3 +-
>>>    include/linux/printk.h                    |  16 +++++
>>>    lib/Kconfig                               |  16 +++++
>>>    lib/vsprintf.c                            | 113
>>>    ++++++++++++++++++++++++++++++
>>>    5 files changed, 162 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/Documentation/core-api/printk-formats.rst
>>> b/Documentation/core-api/printk-formats.rst
>>> index 8ebe46b1..5c66097 100644
>>> --- a/Documentation/core-api/printk-formats.rst
>>> +++ b/Documentation/core-api/printk-formats.rst
>>> @@ -545,6 +545,21 @@ For printing netdev_features_t.
>>>    
>>>    Passed by reference.
>>>    
>>> +BTF-based printing of pointer data
>>> +----------------------------------
>>> +If '%pT' is specified, use the struct btf_ptr * along with kernel vmlinux
>>> +BPF Type Format (BTF) to show the typed data.  For example, specifying
>>> +
>>> +	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct_sk_buff));
>>> +
>>> +will utilize BTF information to traverse the struct sk_buff * and display
>>> it.
>>> +
>>> +Supported modifers are
>>> + 'c' compact output (no indentation, newlines etc)
>>> + 'N' do not show type names
>>> + 'x' show raw pointers (no obfuscation)
>>> + '0' show zero-valued data (it is not shown by default)
>>> +
>>>    Thanks
>>>    ======
>>>    
>>> diff --git a/include/linux/btf.h b/include/linux/btf.h
>>> index d571125..7b585ab 100644
>>> --- a/include/linux/btf.h
>>> +++ b/include/linux/btf.h
>>> @@ -169,10 +169,11 @@ static inline const struct btf_member
>>> *btf_type_member(const struct btf_type *t)
>>>    	return (const struct btf_member *)(t + 1);
>>>    }
>>>    
>>> +struct btf *btf_parse_vmlinux(void);
>>> +
>>>    #ifdef CONFIG_BPF_SYSCALL
>>>    const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
>>>    const char *btf_name_by_offset(const struct btf *btf, u32 offset);
>>> -struct btf *btf_parse_vmlinux(void);
>>>    struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog);
>>>    #else
>>>    static inline const struct btf_type *btf_type_by_id(const struct btf *btf,
>>> diff --git a/include/linux/printk.h b/include/linux/printk.h
>>> index fcde0772..3c3ea53 100644
>>> --- a/include/linux/printk.h
>>> +++ b/include/linux/printk.h
>>> @@ -528,4 +528,20 @@ static inline void print_hex_dump_debug(const char
>>> *prefix_str, int prefix_type,
>>>    #define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
>>>     print_hex_dump_debug(prefix_str, prefix_type, 16, 1, buf, len, true)
>>>    +/**
>>> + * struct btf_ptr is used for %pT (typed pointer) display; the
>>> + * additional type string/BTF id are used to render the pointer
>>> + * data as the appropriate type.
>>> + */
>>> +struct btf_ptr {
>>> +	void *ptr;
>>> +	const char *type;
>>> +	u32 id;
>>> +};
>>> +
>>> +#define	BTF_PTR_TYPE(ptrval, typeval) \
>>> +	(&((struct btf_ptr){.ptr = ptrval, .type = #typeval}))
>>> +
>>> +#define BTF_PTR_ID(ptrval, idval) \
>>> +	(&((struct btf_ptr){.ptr = ptrval, .id = idval}))
>>>    #endif
>> [...]
>>> diff --git a/lib/vsprintf.c b/lib/vsprintf.c
>>> index 7c488a1..f9276f8 100644
>>> --- a/lib/vsprintf.c
>>> +++ b/lib/vsprintf.c
>>> @@ -43,6 +43,7 @@
>>>    #ifdef CONFIG_BLOCK
>>>    #include <linux/blkdev.h>
>>>    #endif
>>> +#include <linux/btf.h>
>>>    
>>>    #include "../mm/internal.h"	/* For the trace_print_flags arrays */
>>>    
>>> @@ -2059,6 +2060,103 @@ char *fwnode_string(char *buf, char *end, struct
>>> fwnode_handle *fwnode,
>>>    	return widen_string(buf, buf - buf_start, end, spec);
>>>    }
>>>    
>>> +#if IS_ENABLED(CONFIG_BTF_PRINTF)
>>> +#define btf_modifier_flag(c)	(c == 'c' ? BTF_SHOW_COMPACT :	\
>>> +				 c == 'N' ? BTF_SHOW_NONAME :	\
>>> +				 c == 'x' ? BTF_SHOW_PTR_RAW :	\
>>> +				 c == '0' ? BTF_SHOW_ZERO : 0)
>>> +
>>> +static noinline_for_stack
>>> +char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
>>> +		 const char *fmt)
>>> +{
>>> +	struct btf_ptr *bp = (struct btf_ptr *)ptr;
>>> +	u8 btf_kind = BTF_KIND_TYPEDEF;
>>> +	const struct btf_type *t;
>>> +	const struct btf *btf;
>>> +	char *buf_start = buf;
>>> +	const char *btf_type;
>>> +	u64 flags = 0, mod;
>>> +	s32 btf_id;
>>> +
>>> +	if (check_pointer(&buf, end, ptr, spec))
>>> +		return buf;
>>> +
>>> +	if (check_pointer(&buf, end, bp->ptr, spec))
>>> +		return buf;
>>> +
>>> +	while (isalnum(*fmt)) {
>>> +		mod = btf_modifier_flag(*fmt);
>>> +		if (!mod)
>>> +			break;
>>> +		flags |= mod;
>>> +		fmt++;
>>> +	}
>>> +
>>> +	btf = bpf_get_btf_vmlinux();
>>> +	if (IS_ERR_OR_NULL(btf))
>>> +		return ptr_to_id(buf, end, bp->ptr, spec);
>>> +
>>> +	if (bp->type != NULL) {
>>> +		btf_type = bp->type;
>>> +
>>> +		if (strncmp(bp->type, "struct ", strlen("struct ")) == 0) {
>>> +			btf_kind = BTF_KIND_STRUCT;
>>> +			btf_type += strlen("struct ");
>>> +		} else if (strncmp(btf_type, "union ", strlen("union ")) == 0)
>>> {
>>> +			btf_kind = BTF_KIND_UNION;
>>> +			btf_type += strlen("union ");
>>> +		} else if (strncmp(btf_type, "enum ", strlen("enum ")) == 0) {
>>> +			btf_kind = BTF_KIND_ENUM;
>>> +			btf_type += strlen("enum ");
>>> +		}
>>
>> I think typedef should be supported here.
>> In kernel, we have some structure directly defined as typedef's.
>> A lot of internal int types also typedefs, like u32, atomic_t,
>> possible_net_t, etc.
>>
>> A type name without prefix "struct", "union", "enum" can be
>> treated as a typedef first.
>>
> 
> That's how the code works today; we start with a typedef assumption.
> See the comment below starting "Assume type specified is a typedef";
> we initialize btf_kind to be a typedef above; it's only changed
> to an BTF_KIND_INT if we find a struct/enum/union prefix or if lookup
> using the typedef kind fails. I should probably make this clearer
> though (move the comment up maybe?). Thanks for taking a look!

Thanks for explanation. I missed it. Move the comments up about what to 
support explicitly will be good.

> 
>> If the type name is not a typedef, it is then compared to a limited
>> number of C basic int types like "char", "unsigned char", "short",
>> "unsigned short", ...
>>
>>> +
>>> +		if (strlen(btf_type) == 0)
>>> +			return ptr_to_id(buf, end, bp->ptr, spec);
>>> +
>>> +		/*
>>> +		 * Assume type specified is a typedef as there's not much
>>> +		 * benefit in specifying int types other than wasting time
>>> +		 * on BTF lookups; we optimize for the most useful path.
>>> +		 *
>>> +		 * Fall back to BTF_KIND_INT if this fails.
>>> +		 */
>>> +		btf_id = btf_find_by_name_kind(btf, btf_type, btf_kind);
>>> +		if (btf_id < 0)
>>> +			btf_id = btf_find_by_name_kind(btf, btf_type,
>>> +						       BTF_KIND_INT);
>>> +	} else if (bp->id > 0)
>>> +		btf_id = bp->id;
>>> +	else
>>> +		return ptr_to_id(buf, end, bp->ptr, spec);
>>> +
>>> +	if (btf_id > 0)
>>> +		t = btf_type_by_id(btf, btf_id);
>>> +	if (btf_id <= 0 || !t)
>>> +		return ptr_to_id(buf, end, bp->ptr, spec);
>>> +
>>> +	buf += btf_type_snprintf_show(btf, btf_id, bp->ptr, buf,
>>> +				      end - buf_start, flags);
>>> +
>>> +	return widen_string(buf, buf - buf_start, end, spec);
>>> +}
[...]
diff mbox series

Patch

diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 8ebe46b1..5c66097 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -545,6 +545,21 @@  For printing netdev_features_t.
 
 Passed by reference.
 
+BTF-based printing of pointer data
+----------------------------------
+If '%pT' is specified, use the struct btf_ptr * along with kernel vmlinux
+BPF Type Format (BTF) to show the typed data.  For example, specifying
+
+	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct_sk_buff));
+
+will utilize BTF information to traverse the struct sk_buff * and display it.
+
+Supported modifers are
+ 'c' compact output (no indentation, newlines etc)
+ 'N' do not show type names
+ 'x' show raw pointers (no obfuscation)
+ '0' show zero-valued data (it is not shown by default)
+
 Thanks
 ======
 
diff --git a/include/linux/btf.h b/include/linux/btf.h
index d571125..7b585ab 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -169,10 +169,11 @@  static inline const struct btf_member *btf_type_member(const struct btf_type *t)
 	return (const struct btf_member *)(t + 1);
 }
 
+struct btf *btf_parse_vmlinux(void);
+
 #ifdef CONFIG_BPF_SYSCALL
 const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
 const char *btf_name_by_offset(const struct btf *btf, u32 offset);
-struct btf *btf_parse_vmlinux(void);
 struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog);
 #else
 static inline const struct btf_type *btf_type_by_id(const struct btf *btf,
diff --git a/include/linux/printk.h b/include/linux/printk.h
index fcde0772..3c3ea53 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -528,4 +528,20 @@  static inline void print_hex_dump_debug(const char *prefix_str, int prefix_type,
 #define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
 	print_hex_dump_debug(prefix_str, prefix_type, 16, 1, buf, len, true)
 
+/**
+ * struct btf_ptr is used for %pT (typed pointer) display; the
+ * additional type string/BTF id are used to render the pointer
+ * data as the appropriate type.
+ */
+struct btf_ptr {
+	void *ptr;
+	const char *type;
+	u32 id;
+};
+
+#define	BTF_PTR_TYPE(ptrval, typeval) \
+	(&((struct btf_ptr){.ptr = ptrval, .type = #typeval}))
+
+#define BTF_PTR_ID(ptrval, idval) \
+	(&((struct btf_ptr){.ptr = ptrval, .id = idval}))
 #endif
diff --git a/lib/Kconfig b/lib/Kconfig
index 5d53f96..ac3a513 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -6,6 +6,22 @@ 
 config BINARY_PRINTF
 	def_bool n
 
+config BTF_PRINTF
+	bool "print type information using BPF type format"
+	depends on DEBUG_INFO_BTF
+	default n
+	help
+	  Print structures, unions etc pointed to by pointer argument using
+	  printk() family of functions (vsnprintf, printk, trace_printk, etc).
+	  For example, we can specify
+	  printk(KERN_INFO, "%pT<struct sk_buff>", skb); to print the skb
+	  data structure content, including all nested type data.
+	  Pointers within data structures displayed are not followed, and
+	  are obfuscated where specified in line with normal pointer display.
+	  via printk.
+
+	  Depends on availability of vmlinux BTF information.
+
 menu "Library routines"
 
 config RAID6_PQ
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 7c488a1..f9276f8 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -43,6 +43,7 @@ 
 #ifdef CONFIG_BLOCK
 #include <linux/blkdev.h>
 #endif
+#include <linux/btf.h>
 
 #include "../mm/internal.h"	/* For the trace_print_flags arrays */
 
@@ -2059,6 +2060,103 @@  char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode,
 	return widen_string(buf, buf - buf_start, end, spec);
 }
 
+#if IS_ENABLED(CONFIG_BTF_PRINTF)
+#define btf_modifier_flag(c)	(c == 'c' ? BTF_SHOW_COMPACT :	\
+				 c == 'N' ? BTF_SHOW_NONAME :	\
+				 c == 'x' ? BTF_SHOW_PTR_RAW :	\
+				 c == '0' ? BTF_SHOW_ZERO : 0)
+
+static noinline_for_stack
+char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
+		 const char *fmt)
+{
+	struct btf_ptr *bp = (struct btf_ptr *)ptr;
+	u8 btf_kind = BTF_KIND_TYPEDEF;
+	const struct btf_type *t;
+	const struct btf *btf;
+	char *buf_start = buf;
+	const char *btf_type;
+	u64 flags = 0, mod;
+	s32 btf_id;
+
+	if (check_pointer(&buf, end, ptr, spec))
+		return buf;
+
+	if (check_pointer(&buf, end, bp->ptr, spec))
+		return buf;
+
+	while (isalnum(*fmt)) {
+		mod = btf_modifier_flag(*fmt);
+		if (!mod)
+			break;
+		flags |= mod;
+		fmt++;
+	}
+
+	btf = bpf_get_btf_vmlinux();
+	if (IS_ERR_OR_NULL(btf))
+		return ptr_to_id(buf, end, bp->ptr, spec);
+
+	if (bp->type != NULL) {
+		btf_type = bp->type;
+
+		if (strncmp(bp->type, "struct ", strlen("struct ")) == 0) {
+			btf_kind = BTF_KIND_STRUCT;
+			btf_type += strlen("struct ");
+		} else if (strncmp(btf_type, "union ", strlen("union ")) == 0) {
+			btf_kind = BTF_KIND_UNION;
+			btf_type += strlen("union ");
+		} else if (strncmp(btf_type, "enum ", strlen("enum ")) == 0) {
+			btf_kind = BTF_KIND_ENUM;
+			btf_type += strlen("enum ");
+		}
+
+		if (strlen(btf_type) == 0)
+			return ptr_to_id(buf, end, bp->ptr, spec);
+
+		/*
+		 * Assume type specified is a typedef as there's not much
+		 * benefit in specifying int types other than wasting time
+		 * on BTF lookups; we optimize for the most useful path.
+		 *
+		 * Fall back to BTF_KIND_INT if this fails.
+		 */
+		btf_id = btf_find_by_name_kind(btf, btf_type, btf_kind);
+		if (btf_id < 0)
+			btf_id = btf_find_by_name_kind(btf, btf_type,
+						       BTF_KIND_INT);
+	} else if (bp->id > 0)
+		btf_id = bp->id;
+	else
+		return ptr_to_id(buf, end, bp->ptr, spec);
+
+	if (btf_id > 0)
+		t = btf_type_by_id(btf, btf_id);
+	if (btf_id <= 0 || !t)
+		return ptr_to_id(buf, end, bp->ptr, spec);
+
+	buf += btf_type_snprintf_show(btf, btf_id, bp->ptr, buf,
+				      end - buf_start, flags);
+
+	return widen_string(buf, buf - buf_start, end, spec);
+}
+#else
+static noinline_for_stack
+char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
+	const char *fmt)
+{
+	struct btf_ptr *bp = (struct btf_ptr *)ptr;
+
+	if (check_pointer(&buf, end, ptr, spec))
+		return buf;
+
+	if (check_pointer(&buf, end, bp->ptr, spec))
+		return buf;
+
+	return ptr_to_id(buf, end, bp->ptr, spec);
+}
+#endif /* IS_ENABLED(CONFIG_BTF_PRINTF) */
+
 /*
  * Show a '%p' thing.  A kernel extension is that the '%p' is followed
  * by an extra set of alphanumeric characters that are extended format
@@ -2169,6 +2267,19 @@  char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode,
  *		P node name, including a possible unit address
  * - 'x' For printing the address. Equivalent to "%lx".
  *
+ * - 'T[cNx0]' For printing struct btf_ptr * data using BPF Type Format (BTF).
+ *
+ *			Optional arguments are
+ *			c		compact (no indentation/newlines)
+ *			N		do not print type and member names
+ *			x		do not obfuscate pointers
+ *			0		show 0-valued data
+ *
+ *    BPF_PTR_TYPE(ptr, type) can be used to place pointer and type string
+ *    in the "struct btf_ptr *" expected; for example:
+ *
+ *	printk(KERN_INFO "%pT", BTF_PTR_TYPE(skb, struct sk_buff));
+ *
  * ** When making changes please also update:
  *	Documentation/core-api/printk-formats.rst
  *
@@ -2251,6 +2362,8 @@  char *pointer(const char *fmt, char *buf, char *end, void *ptr,
 		if (!IS_ERR(ptr))
 			break;
 		return err_ptr(buf, end, ptr, spec);
+	case 'T':
+		return btf_string(buf, end, ptr, spec, fmt + 1);
 	}
 
 	/* default is to _not_ leak addresses, hash before printing */