[v3,bpf-next,6/8] printk: extend test_printf to test %pT BTF-based format specifier

Message ID 1592914031-31049-7-git-send-email-alan.maguire@oracle.com
State New
Headers show
Series
  • bpf, printk: add BTF-based type printing
Related show

Commit Message

Alan Maguire June 23, 2020, 12:07 p.m.
Add tests to verify basic type display and to iterate through all
enums, structs, unions and typedefs ensuring expected behaviour
occurs.  Since test_printf can be built as a module we need to
export a BTF kind iterator function to allow us to iterate over
all names of a particular BTF kind.

These changes add up to approximately 20,000 new tests covering
all enum, struct, union and typedefs in vmlinux BTF.

Individual tests are also added for int, char, struct, enum
and typedefs which verify output is as expected.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 include/linux/btf.h |   3 +
 kernel/bpf/btf.c    |  33 ++++++
 lib/test_printf.c   | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 352 insertions(+)

Patch

diff --git a/include/linux/btf.h b/include/linux/btf.h
index e8dbf0c..e3102a7 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -191,4 +191,7 @@  static inline const char *btf_name_by_offset(const struct btf *btf,
 }
 #endif
 
+/* Following function used for testing BTF-based printk-family support */
+const char *btf_vmlinux_next_type_name(u8 kind, s32 *id);
+
 #endif
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index c82cb18..4e250cd 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -5459,3 +5459,36 @@  u32 btf_id(const struct btf *btf)
 {
 	return btf->id;
 }
+
+/*
+ * btf_vmlinux_next_type_name():  used in test_printf.c to
+ * iterate over types for testing.
+ * Exported as test_printf can be built as a module.
+ *
+ * @kind: BTF_KIND_* value
+ * @id: pointer to last id; value/result argument. When next
+ *      type name is found, we set *id to associated id.
+ * Returns:
+ *	Next type name, sets *id to associated id.
+ */
+const char *btf_vmlinux_next_type_name(u8 kind, s32 *id)
+{
+	const struct btf *btf = bpf_get_btf_vmlinux();
+	const struct btf_type *t;
+	const char *name;
+
+	if (!btf || !id)
+		return NULL;
+
+	for ((*id)++; *id <= btf->nr_types; (*id)++) {
+		t = btf->types[*id];
+		if (BTF_INFO_KIND(t->info) != kind)
+			continue;
+		name = btf_name_by_offset(btf, t->name_off);
+		if (name && strlen(name) > 0)
+			return name;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(btf_vmlinux_next_type_name);
diff --git a/lib/test_printf.c b/lib/test_printf.c
index 7ac87f1..7ce7387 100644
--- a/lib/test_printf.c
+++ b/lib/test_printf.c
@@ -23,6 +23,9 @@ 
 #include <linux/mm.h>
 
 #include <linux/property.h>
+#include <linux/bpf.h>
+#include <linux/btf.h>
+#include <linux/skbuff.h>
 
 #include "../tools/testing/selftests/kselftest_module.h"
 
@@ -669,6 +672,318 @@  static void __init fwnode_pointer(void)
 #endif
 }
 
+#define	__TEST_BTF(fmt, type, ptr, expected)				       \
+	test(expected, "%pT"fmt, ptr)
+
+#define TEST_BTF_C(type, var, ...)					       \
+	do {								       \
+		type var = __VA_ARGS__;					       \
+		struct btf_ptr *ptr = BTF_PTR_TYPE(&var, type);		       \
+		pr_debug("type %s: %pTc", #type, ptr);			       \
+		__TEST_BTF("c", type, ptr, "(" #type ")" #__VA_ARGS__);	       \
+	} while (0)
+
+#define TEST_BTF(fmt, type, var, expected, ...)				       \
+	do {								       \
+		type var = __VA_ARGS__;					       \
+		struct btf_ptr *ptr = BTF_PTR_TYPE(&var, type);		       \
+		pr_debug("type %s: %pT"fmt, #type, ptr);		       \
+		__TEST_BTF(fmt, type, ptr, expected);			       \
+	} while (0)
+
+#define	BTF_MAX_DATA_SIZE	65536
+
+static void __init
+btf_print_kind(u8 kind, const char *kind_name, u64 fillval)
+{
+	const char *fmt1 = "%pT", *fmt2 = "%pTN", *fmt3 = "%pT0";
+	const char *name, *fmt = fmt1;
+	int i, res1, res2, res3, res4;
+	char type_name[256];
+	char *buf, *buf2;
+	u8 *dummy_data;
+	s32 id = 0;
+
+	dummy_data = kzalloc(BTF_MAX_DATA_SIZE, GFP_KERNEL);
+
+	/* fill our dummy data with supplied fillval. */
+	for (i = 0; i < BTF_MAX_DATA_SIZE; i++)
+		dummy_data[i] = fillval;
+
+	buf = kzalloc(BTF_MAX_DATA_SIZE, GFP_KERNEL);
+	buf2 = kzalloc(BTF_MAX_DATA_SIZE, GFP_KERNEL);
+
+	for (;;) {
+		name = btf_vmlinux_next_type_name(kind, &id);
+		if (!name)
+			break;
+
+		total_tests++;
+
+		snprintf(type_name, sizeof(type_name), "%s%s",
+			 kind_name, name);
+
+		res1 = snprintf(buf, BTF_MAX_DATA_SIZE, fmt1,
+				BTF_PTR_TYPE(dummy_data, type_name));
+		res2 = snprintf(buf, 0, fmt1,
+				BTF_PTR_TYPE(dummy_data, type_name));
+		res3 = snprintf(buf, BTF_MAX_DATA_SIZE, fmt2,
+				BTF_PTR_TYPE(dummy_data, type_name));
+		res4 = snprintf(buf, BTF_MAX_DATA_SIZE, fmt3,
+				BTF_PTR_TYPE(dummy_data, type_name));
+
+		(void) snprintf(buf, BTF_MAX_DATA_SIZE, "%pT",
+				BTF_PTR_TYPE(dummy_data, type_name));
+		(void) snprintf(buf2, BTF_MAX_DATA_SIZE, "%pT",
+				BTF_PTR_TYPE(dummy_data, type_name));
+
+		/*
+		 * Ensure return value is > 0 and identical irrespective
+		 * of whether we pass in a big enough buffer;
+		 * also ensure that printing names always results in as
+		 * long/longer buffer length.
+		 */
+		if (res1 <= 0 || res2 <= 0 || res3 <= 0 || res4 <= 0) {
+			if (res3 <= 0)
+				fmt = fmt2;
+			if (res4 <= 0)
+				fmt = fmt3;
+
+			pr_warn("snprintf(%s%s); %d <= 0 (fmt %s)",
+				kind_name, name,
+				res1 <= 0 ? res1 : res2 <= 0 ? res2 :
+				res3 <= 0 ? res3 : res4, fmt);
+			failed_tests++;
+		} else if (res1 != res2) {
+			pr_warn("snprintf(%s%s): %d (to buf) != %d (no buf)",
+				kind_name, name, res1, res2);
+			failed_tests++;
+		} else if (res3 > res2) {
+			pr_warn("snprintf(%s%s); %d (no names) > %d (names)",
+				kind_name, name, res3, res2);
+			failed_tests++;
+		} else if (strcmp(buf, buf2) != 0) {
+			/* Safe and unsafe buffers should match. */
+			pr_warn("snprintf(%s%s); safe != unsafe",
+				kind_name, name);
+			pr_warn("safe: %s", buf);
+			pr_warn("unsafe: %s", buf2);
+			failed_tests++;
+		} else {
+			pr_debug("Printed %s%s (%d bytes)",
+				 kind_name, name, res1);
+		}
+	}
+	kfree(dummy_data);
+	kfree(buf);
+	kfree(buf2);
+}
+
+/*
+ * For BTF it is the struct btf_ptr * ptr field, not the pointer itself
+ * which gets displayed, so it is that we need to hash.
+ */
+static void __init
+test_btf_hashed(const char *fmt, struct btf_ptr *p)
+{
+	char buf[PLAIN_BUF_SIZE];
+	int ret;
+
+	ret = plain_hash_to_buffer(p->ptr, buf, PLAIN_BUF_SIZE);
+	if (ret)
+		return;
+
+	test(buf, fmt, p);
+}
+
+#ifdef CONFIG_DEBUG_INFO_BTF
+
+static void __init btf_pointer_test_int(void)
+{
+	/* simple int */
+	TEST_BTF_C(int, testint, 1234);
+	TEST_BTF("cN", int, testint, "1234", 1234);
+	/* zero value should be printed at toplevel */
+	TEST_BTF("c", int, testint, "(int)0", 0);
+	TEST_BTF("cN", int, testint, "0", 0);
+	TEST_BTF("c0", int, testint, "(int)0", 0);
+	TEST_BTF("cN0", int, testint, "0", 0);
+	TEST_BTF_C(int, testint, -4567);
+	TEST_BTF("cN", int, testint, "-4567", -4567);
+}
+
+static void __init btf_pointer_test_char(void)
+{
+	/* simple char */
+	TEST_BTF_C(char, testchar, 100);
+	TEST_BTF("cN", char, testchar, "100", 100);
+	/* zero value should be printed at toplevel */
+	TEST_BTF("c", char, testchar, "(char)0", 0);
+	TEST_BTF("cN", char, testchar, "0", 0);
+	TEST_BTF("c0", char, testchar, "(char)0", 0);
+	TEST_BTF("cN0", char, testchar, "0", 0);
+}
+
+static void __init btf_pointer_test_typedef(void)
+{
+	/* simple typedef */
+	TEST_BTF_C(phys_addr_t, testtype, 100);
+	TEST_BTF("cN", phys_addr_t, testtype, "1", 1);
+	/* zero value should be printed at toplevel */
+	TEST_BTF("c", phys_addr_t, testtype, "(phys_addr_t)0", 0);
+	TEST_BTF("cN", phys_addr_t, testtype, "0", 0);
+	TEST_BTF("c0", phys_addr_t, testtype, "(phys_addr_t)0", 0);
+	TEST_BTF("cN0", phys_addr_t, testtype, "0", 0);
+
+	/* typedef struct */
+	TEST_BTF_C(atomic_t, testtype, {.counter = (int)1,});
+	TEST_BTF("cN", atomic_t, testtype, "{1,}", {.counter = 1,});
+	/* typedef with 0 value should be printed at toplevel */
+	TEST_BTF("c", atomic_t, testtype, "(atomic_t){}", {.counter = 0,});
+	TEST_BTF("cN", atomic_t, testtype, "{}", {.counter = 0,});
+	TEST_BTF("c0", atomic_t, testtype, "(atomic_t){.counter = (int)0,}",
+		 {.counter = 0,});
+	TEST_BTF("cN0", atomic_t, testtype, "{0,}", {.counter = 0,});
+
+	/* typedef array */
+	TEST_BTF("c", cpumask_t, testtype,
+		 "(cpumask_t){.bits = (long unsigned int[])[1,],}",
+		 { .bits = {1,}});
+	TEST_BTF("cN", cpumask_t, testtype, "{[1,],}",
+		 { .bits = {1,}});
+	/* typedef with 0 value should be printed at toplevel */
+	TEST_BTF("c", cpumask_t, testtype, "(cpumask_t){}", {{ 0 }});
+}
+
+static void __init btf_pointer_test_enum(void)
+{
+	/* enum where enum value does (and does not) exist */
+	TEST_BTF_C(enum bpf_cmd, testenum, BPF_MAP_CREATE);
+	TEST_BTF("c", enum bpf_cmd, testenum, "(enum bpf_cmd)BPF_MAP_CREATE",
+		 0);
+	TEST_BTF("cN", enum bpf_cmd, testenum, "BPF_MAP_CREATE",
+		 BPF_MAP_CREATE);
+	TEST_BTF("cN0", enum bpf_cmd, testenum, "BPF_MAP_CREATE", 0);
+
+	TEST_BTF("c0", enum bpf_cmd, testenum, "(enum bpf_cmd)BPF_MAP_CREATE",
+		 BPF_MAP_CREATE);
+	TEST_BTF("cN0", enum bpf_cmd, testenum, "BPF_MAP_CREATE",
+		 BPF_MAP_CREATE);
+	TEST_BTF_C(enum bpf_cmd, testenum, 2000);
+	TEST_BTF("cN", enum bpf_cmd, testenum, "2000", 2000);
+}
+
+static void __init btf_pointer_test_struct(void)
+{
+	/* simple struct */
+	TEST_BTF_C(struct btf_enum, teststruct,
+		   {.name_off = (__u32)3,.val = (__s32)-1,});
+	TEST_BTF("cN", struct btf_enum, teststruct, "{3,-1,}",
+		 { .name_off = 3, .val = -1,});
+	TEST_BTF("cN", struct btf_enum, teststruct, "{-1,}",
+		 { .name_off = 0, .val = -1,});
+	TEST_BTF("cN0", struct btf_enum, teststruct, "{0,-1,}",
+		 { .name_off = 0, .val = -1,});
+	/* empty struct should be printed */
+	TEST_BTF("c", struct btf_enum, teststruct, "(struct btf_enum){}",
+		 { .name_off = 0, .val = 0,});
+	TEST_BTF("cN", struct btf_enum, teststruct, "{}",
+		 { .name_off = 0, .val = 0,});
+	TEST_BTF("c0", struct btf_enum, teststruct,
+		 "(struct btf_enum){.name_off = (__u32)0,.val = (__s32)0,}",
+		 { .name_off = 0, .val = 0,});
+
+	/* struct with pointers */
+	TEST_BTF("cx", struct skb_shared_info, testptr,
+		 "(struct skb_shared_info){.frag_list = (struct sk_buff *)0x0000000000000001,}",
+		 { .frag_list = (struct sk_buff *)1 });
+	/* NULL pointer should not be displayed */
+	TEST_BTF("cx", struct skb_shared_info, testptr,
+		 "(struct skb_shared_info){}",
+		 { .frag_list = (struct sk_buff *)0 });
+
+	/* struct with char array */
+	TEST_BTF("c", struct bpf_prog_info, teststruct,
+		 "(struct bpf_prog_info){.name = (char[])['f','o','o',],}",
+		 { .name = "foo",});
+	TEST_BTF("cN", struct bpf_prog_info, teststruct,
+		 "{['f','o','o',],}",
+		 {.name = "foo",});
+	/* leading null char means do not display string */
+	TEST_BTF("c", struct bpf_prog_info, teststruct,
+		 "(struct bpf_prog_info){}",
+		 {.name = {'\0', 'f', 'o', 'o'}});
+	/* handle non-printable characters */
+	TEST_BTF("c", struct bpf_prog_info, teststruct,
+		 "(struct bpf_prog_info){.name = (char[])[1,2,3,],}",
+		 { .name = {1, 2, 3, 0}});
+
+	/* struct with non-char array */
+	TEST_BTF("c", struct __sk_buff, teststruct,
+		 "(struct __sk_buff){.cb = (__u32[])[1,2,3,4,5,],}",
+		 { .cb = {1, 2, 3, 4, 5,},});
+	TEST_BTF("cN", struct __sk_buff, teststruct,
+		 "{[1,2,3,4,5,],}",
+		 { .cb = { 1, 2, 3, 4, 5},});
+	/* For non-char, arrays, show non-zero values only */
+	TEST_BTF("c", struct __sk_buff, teststruct,
+		 "(struct __sk_buff){.cb = (__u32[])[1,],}",
+		 { .cb = { 0, 0, 1, 0, 0},});
+
+	/* struct with struct array */
+	TEST_BTF("c", struct bpf_struct_ops, teststruct,
+		 "(struct bpf_struct_ops){.func_models = (struct btf_func_model[])[(struct btf_func_model){.ret_size = (u8)1,.nr_args = (u8)2,.arg_size = (u8[])[3,4,5,],},],}",
+		 { .func_models = {{ .ret_size = 1, .nr_args = 2,
+				     .arg_size = { 3, 4, 5,},}}});
+
+	/* struct with bitfields */
+	TEST_BTF_C(struct bpf_insn, testbitfield,
+		   {.code = (__u8)1,.dst_reg = (__u8)0x2,.src_reg = (__u8)0x3,.off = (__s16)4,.imm = (__s32)5,});
+	TEST_BTF("cN", struct bpf_insn, testbitfield, "{1,0x2,0x3,4,5,}",
+		 {.code = 1, .dst_reg = 0x2, .src_reg = 0x3, .off = 4,
+		  .imm = 5,});
+
+	/* struct with anon struct/unions */
+	TEST_BTF("cx", struct sk_buff, test_anon,
+		 "(struct sk_buff){(union){(struct){(union){.dev = (struct net_device *)0x0000000000000001,.dev_scratch = (long unsigned int)1,},},.rbnode = (struct rb_node){.rb_left = (struct rb_node *)0x0000000000000001,},},}",
+		 { .dev_scratch = 1 });
+}
+
+#endif /* CONFIG_DEBUG_INFO_BTF */
+
+static void __init
+btf_pointer(void)
+{
+	struct sk_buff *skb = alloc_skb(64, GFP_KERNEL);
+	u64 fillvals[] = { 0x0, 0xffffffffffffffff, 0x0123456789abcdef };
+	int i;
+
+#ifdef CONFIG_DEBUG_INFO_BTF
+	btf_pointer_test_int();
+	btf_pointer_test_char();
+	btf_pointer_test_typedef();
+	btf_pointer_test_enum();
+	btf_pointer_test_struct();
+#endif /* CONFIG_DEBUG_INFO_BTF */
+
+	/*
+	 * Iterate every instance of each kind, printing each associated type.
+	 * This constitutes around 10k tests.
+	 */
+	for (i = 0; i < ARRAY_SIZE(fillvals); i++) {
+		btf_print_kind(BTF_KIND_STRUCT, "struct ", fillvals[i]);
+		btf_print_kind(BTF_KIND_UNION, "union ", fillvals[i]);
+		btf_print_kind(BTF_KIND_ENUM, "enum ", fillvals[i]);
+		btf_print_kind(BTF_KIND_TYPEDEF, "", fillvals[i]);
+	}
+
+	/* verify unknown type falls back to hashed pointer display */
+	test("(null)", "%pT", BTF_PTR_TYPE(NULL, "unknown_type"));
+	test_btf_hashed("%pT", BTF_PTR_TYPE(skb, "unknown_type"));
+
+	kfree_skb(skb);
+}
+
 static void __init
 test_pointer(void)
 {
@@ -694,6 +1009,7 @@  static void __init fwnode_pointer(void)
 	flags();
 	errptr();
 	fwnode_pointer();
+	btf_pointer();
 }
 
 static void __init selftest(void)