linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Alan Maguire <alan.maguire@oracle.com>
To: ast@kernel.org, daniel@iogearbox.net, yhs@fb.com
Cc: 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,
	bpf@vger.kernel.org, Alan Maguire <alan.maguire@oracle.com>
Subject: [RFC PATCH bpf-next 5/6] printk: add type-printing %pT<type> format specifier which uses BTF
Date: Fri, 17 Apr 2020 11:42:39 +0100	[thread overview]
Message-ID: <1587120160-3030-6-git-send-email-alan.maguire@oracle.com> (raw)
In-Reply-To: <1587120160-3030-1-git-send-email-alan.maguire@oracle.com>

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 a suffix
enclosed <like this> specifies the type, for example, specifying

	printk("%pT<struct sk_buff>", skb)

...will utilize BTF information to traverse the struct 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 compact, specifying values only, but the
'N' modifier can be used to show field names to more easily
track values.  Pointer values are obfuscated as usual.  As
an example:

  struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
  pr_info("%pTN<struct sk_buff>", skb);

...gives us:

{{{.next=00000000c7916e9c,.prev=00000000c7916e9c,{.dev=00000000c7916e9c|.dev_scratch=0}}|.rbnode={.__rb_parent_color=0,.rb_right=00000000c7916e9c,.rb_left=00000000c7916e9c}|.list={.next=00000000c7916e9c,.prev=00000000c7916e9c}},{.sk=00000000c7916e9c|.ip_defrag_offset=0},{.tstamp=0|.skb_mstamp_ns=0},.cb=['\0'],{{._skb_refdst=0,.destructor=00000000c7916e9c}|.tcp_tsorted_anchor={.next=00000000c7916e9c,.prev=00000000c7916e9c}},._nfct=0,.len=0,.data_len=0,.mac_len=0,.hdr_len=0,.queue_mapping=0,.__cloned_offset=[],.cloned=0x0,.nohdr=0x0,.fclone=0x0,.peeked=0x0,.head_frag=0x0,.pfmemalloc=0x0,.active_extensions=0,.headers_start=[],.__pkt_type_offset=[],.pkt_type=0x0,.ignore_df=0x0,.nf_trace=0x0,.ip_summed=0x0,.ooo_okay=0x0,.l4_hash=0x0,.sw_hash=0x0,.wifi_acked_valid=0x0,.wifi_acked=0x0,.no_fcs=0x0,.encapsulation=0x0,.encap_hdr_csum=0x0,.csum_valid=0x0,.__pkt_vlan_present_offset=[],.vlan_present=0x0,.csum_complete_sw=0x0,.csum_level=0x0,.csum_not_inet=0x0,.dst_pending_co

printk output is truncated at 1024 bytes.  For such cases, the compact
display mode (minus the field info) may be used. "|" differentiates
between different union members.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 Documentation/core-api/printk-formats.rst |   8 ++
 include/linux/btf.h                       |   3 +-
 lib/Kconfig                               |  16 ++++
 lib/vsprintf.c                            | 145 +++++++++++++++++++++++++++++-
 4 files changed, 169 insertions(+), 3 deletions(-)

diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 8ebe46b1..b786577 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -545,6 +545,14 @@ For printing netdev_features_t.
 
 Passed by reference.
 
+BTF-based printing of pointer data
+----------------------------------
+If '%pT[N]<type_name>' is specified, use the BPF Type Format (BTF) to
+show the typed data.  For example, specifying '%pT<struct sk_buff>' will utilize
+BTF information to traverse the struct sk_buff * and display it.
+
+Supported modifer is 'N' (show type field names).
+
 Thanks
 ======
 
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 2f78dc8..456bd8f 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -158,10 +158,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/lib/Kconfig b/lib/Kconfig
index bc7e563..e92109e 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..43e06f3 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,127 @@ char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode,
 	return widen_string(buf, buf - buf_start, end, spec);
 }
 
+#define is_btf_fmt_start(c)	(c == 'T')
+#define is_btf_type_start(c)	(c == '<')
+#define is_btf_type_end(c)	(c == '>')
+
+#define btf_modifier_flag(c)	(c == 'N' ? BTF_SHOW_NAME : 0)
+
+static noinline_for_stack
+const char *skip_btf_type(const char *fmt, bool *found_btf_type)
+{
+	*found_btf_type = false;
+
+	if (!is_btf_fmt_start(*fmt))
+		return fmt;
+	fmt++;
+
+	while (btf_modifier_flag(*fmt))
+		fmt++;
+
+	if (!is_btf_type_start(*fmt))
+		return fmt;
+
+	while (!is_btf_type_end(*fmt) && *fmt != '\0')
+		fmt++;
+
+	if (is_btf_type_end(*fmt)) {
+		fmt++;
+		*found_btf_type = true;
+	}
+
+	return fmt;
+}
+
+static noinline_for_stack
+char *btf_string(char *buf, char *end, void *ptr, struct printf_spec spec,
+		 const char *fmt)
+{
+	const struct btf_type *btf_type;
+	char btf_name[KSYM_SYMBOL_LEN];
+	u8 btf_kind = BTF_KIND_TYPEDEF;
+	const struct btf *btf;
+	char *buf_start = buf;
+	u64 flags = 0, mod;
+	s32 btf_id;
+	int i;
+
+	/*
+	 * Accepted format is [format_modifiers]*<type> ;
+	 * for example "%pTN<struct sk_buff>" will show a representation
+	 * of the sk_buff pointed to by the associated argument including
+	 * member names.
+	 */
+	if (check_pointer(&buf, end, ptr, spec))
+		return buf;
+
+	while (isalpha(*fmt)) {
+		mod = btf_modifier_flag(*fmt);
+		if (!mod)
+			break;
+		flags |= mod;
+		fmt++;
+	}
+
+	if (!is_btf_type_start(*fmt))
+		return error_string(buf, end, "(%pT?)", spec);
+	fmt++;
+
+	if (isspace(*fmt))
+		fmt = skip_spaces(++fmt);
+
+	if (strncmp(fmt, "struct ", strlen("struct ")) == 0) {
+		btf_kind = BTF_KIND_STRUCT;
+		fmt += strlen("struct ");
+	} else if (strncmp(fmt, "union ", strlen("union ")) == 0) {
+		btf_kind = BTF_KIND_UNION;
+		fmt += strlen("union ");
+	} else if (strncmp(fmt, "enum ", strlen("enum ")) == 0) {
+		btf_kind = BTF_KIND_ENUM;
+		fmt += strlen("enum ");
+	}
+
+	if (isspace(*fmt))
+		fmt = skip_spaces(++fmt);
+
+	for (i = 0; isalnum(*fmt) || *fmt == '_'; fmt++, i++)
+		btf_name[i] = *fmt;
+
+	btf_name[i] = '\0';
+
+	if (isspace(*fmt))
+		fmt = skip_spaces(++fmt);
+
+	if (strlen(btf_name) == 0 || !is_btf_type_end(*fmt))
+		return error_string(buf, end, "(%pT?)", spec);
+
+	btf = bpf_get_btf_vmlinux();
+	if (IS_ERR_OR_NULL(btf))
+		return ptr_to_id(buf, end, ptr, spec);
+
+	/*
+	 * Assume type specified is a typedef as there's not much
+	 * benefit in specifying %p<int> 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_name, btf_kind);
+	if (btf_id < 0)
+		btf_id = btf_find_by_name_kind(btf, btf_name,
+					       BTF_KIND_INT);
+
+	if (btf_id >= 0)
+		btf_type = btf_type_by_id(btf, btf_id);
+	if (btf_id < 0 || !btf_type)
+		return ptr_to_id(buf, end, ptr, spec);
+
+	buf += btf_type_snprintf_show(btf, btf_id, ptr, buf,
+				      end - buf_start, flags);
+
+	return widen_string(buf, buf - buf_start, end, spec);
+}
+
 /*
  * 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 +2291,15 @@ 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[N<type_name>]' For printing pointer data using BPF Type Format (BTF).
+ *
+ *			Optional arguments are
+ *			N		print type and member names
+ *
+ *			Required options are
+ *			<type_name>	associated pointer is interpreted
+ *					to point at type_name.
+ *
  * ** When making changes please also update:
  *	Documentation/core-api/printk-formats.rst
  *
@@ -2251,6 +2382,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 */
@@ -2506,6 +2639,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
 	unsigned long long num;
 	char *str, *end;
 	struct printf_spec spec = {0};
+	bool found_btf_type;
 
 	/* Reject out-of-range values early.  Large positive sizes are
 	   used for unknown buffer sizes. */
@@ -2577,8 +2711,15 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
 		case FORMAT_TYPE_PTR:
 			str = pointer(fmt, str, end, va_arg(args, void *),
 				      spec);
-			while (isalnum(*fmt))
-				fmt++;
+			/*
+			 * BTF type info is enclosed <like this>, so can
+			 * contain whitespace.
+			 */
+			fmt = skip_btf_type(fmt, &found_btf_type);
+			if (!found_btf_type) {
+				while (isalnum(*fmt))
+					fmt++;
+			}
 			break;
 
 		case FORMAT_TYPE_PERCENT_CHAR:
-- 
1.8.3.1


  parent reply	other threads:[~2020-04-17 10:43 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-17 10:42 [RFC PATCH bpf-next 0/6] bpf, printk: add BTF-based type printing Alan Maguire
2020-04-17 10:42 ` [RFC PATCH bpf-next 1/6] bpf: provide function to get vmlinux BTF information Alan Maguire
2020-04-17 10:42 ` [RFC PATCH bpf-next 2/6] bpf: btf->resolved_[ids,sizes] should not be used for vmlinux BTF Alan Maguire
2020-04-17 10:42 ` [RFC PATCH bpf-next 3/6] bpf: move to generic BTF show support, apply it to seq files/strings Alan Maguire
2020-04-17 10:42 ` [RFC PATCH bpf-next 4/6] checkpatch: add new BTF pointer format specifier Alan Maguire
2020-04-17 10:42 ` Alan Maguire [this message]
2020-04-29 12:09   ` [RFC PATCH bpf-next 5/6] printk: add type-printing %pT<type> format specifier which uses BTF Rasmus Villemoes
2020-04-17 10:42 ` [RFC PATCH bpf-next 6/6] printk: extend test_printf to test %pT BTF-based format specifier Alan Maguire
2020-04-17 16:47 ` [RFC PATCH bpf-next 0/6] bpf, printk: add BTF-based type printing Arnaldo Carvalho de Melo
2020-04-17 17:06   ` Alan Maguire
2020-04-18 16:05 ` Alexei Starovoitov
2020-04-18 20:31   ` Arnaldo Melo
2020-04-20 15:29   ` Alan Maguire
2020-04-20 16:32     ` Joe Perches
2020-04-29 12:15       ` Rasmus Villemoes
2020-04-30 10:03       ` Alan Maguire
2020-05-02  0:25         ` Joe Perches
2020-04-20 20:54   ` Arnaldo Carvalho de Melo

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=1587120160-3030-6-git-send-email-alan.maguire@oracle.com \
    --to=alan.maguire@oracle.com \
    --cc=andriin@fb.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=john.fastabend@gmail.com \
    --cc=kafai@fb.com \
    --cc=kpsingh@chromium.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).