The BPF Type Format (BTF) can be used in conjunction with the helper bpf_snprintf_btf() to display kernel data with type information.
This series generalizes that support and shares it with libbpf so that libbpf can display typed data. BTF display functionality is factored out of kernel/bpf/btf.c into kernel/bpf/btf_show_common.c, and that file is duplicated in tools/lib/bpf. Similarly, common definitions and inline functions needed for this support are extracted into include/linux/btf_common.h and this header is again duplicated in tools/lib/bpf.
Patch 1 carries out the refactoring, for which no kernel changes are intended, and introduces btf__snprintf() a libbpf function that supports dumping a string representation of typed data using the struct btf * and id associated with that type.
Patch 2 tests btf__snprintf() with built-in and kernel types to ensure data is of expected format. The test closely mirrors the BPF program associated with the snprintf_btf.c; in this case however the string representations are verified in userspace rather than in BPF program context.
Alan Maguire (2): bpf: share BTF "show" implementation between kernel and libbpf selftests/bpf: test libbpf-based type display
include/linux/btf.h | 121 +- include/linux/btf_common.h | 286 +++++ kernel/bpf/Makefile | 2 +- kernel/bpf/arraymap.c | 1 + kernel/bpf/bpf_struct_ops.c | 1 + kernel/bpf/btf.c | 1215 +------------------ kernel/bpf/btf_show_common.c | 1218 ++++++++++++++++++++ kernel/bpf/core.c | 1 + kernel/bpf/hashtab.c | 1 + kernel/bpf/local_storage.c | 1 + kernel/bpf/verifier.c | 1 + kernel/trace/bpf_trace.c | 1 + tools/lib/bpf/Build | 2 +- tools/lib/bpf/btf.h | 7 + tools/lib/bpf/btf_common.h | 286 +++++ tools/lib/bpf/btf_show_common.c | 1218 ++++++++++++++++++++ tools/lib/bpf/libbpf.map | 1 + .../selftests/bpf/prog_tests/snprintf_btf_user.c | 192 +++ 18 files changed, 3236 insertions(+), 1319 deletions(-) create mode 100644 include/linux/btf_common.h create mode 100644 kernel/bpf/btf_show_common.c create mode 100644 tools/lib/bpf/btf_common.h create mode 100644 tools/lib/bpf/btf_show_common.c create mode 100644 tools/testing/selftests/bpf/prog_tests/snprintf_btf_user.c
Test btf__snprintf with various base/kernel types and ensure display is as expected; tests are identical to those in snprintf_btf test save for the fact these run in userspace rather than BPF program context.
Signed-off-by: Alan Maguire alan.maguire@oracle.com --- .../selftests/bpf/prog_tests/snprintf_btf_user.c | 192 +++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/snprintf_btf_user.c
diff --git a/tools/testing/selftests/bpf/prog_tests/snprintf_btf_user.c b/tools/testing/selftests/bpf/prog_tests/snprintf_btf_user.c new file mode 100644 index 0000000..9eb82b2 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/snprintf_btf_user.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2021, Oracle and/or its affiliates. */ +#include <test_progs.h> +#include <linux/bpf.h> +#include <bpf/btf.h> + +#include <stdio.h> +#include <string.h> + +#define STRSIZE 2048 +#define EXPECTED_STRSIZE 256 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +/* skip "enum "/"struct " prefixes */ +#define SKIP_PREFIX(_typestr, _prefix) \ + do { \ + if (strstr(_typestr, _prefix) == _typestr) \ + _typestr += strlen(_prefix) + 1; \ + } while (0) + +#define TEST_BTF(btf, _str, _type, _flags, _expected, ...) \ + do { \ + const char _expectedval[EXPECTED_STRSIZE] = _expected; \ + const char __ptrtype[64] = #_type; \ + char *_ptrtype = (char *)__ptrtype; \ + __u64 _hflags = _flags | BTF_F_COMPACT; \ + static _type _ptrdata = __VA_ARGS__; \ + void *_ptr = &_ptrdata; \ + __s32 _type_id; \ + int _cmp, _ret; \ + \ + SKIP_PREFIX(_ptrtype, "enum"); \ + SKIP_PREFIX(_ptrtype, "struct"); \ + SKIP_PREFIX(_ptrtype, "union"); \ + _ptr = &_ptrdata; \ + _type_id = btf__find_by_name(btf, _ptrtype); \ + if (CHECK(_type_id <= 0, "find type id", \ + "no '%s' in BTF: %d\n", _ptrtype, _type_id)) \ + return; \ + _ret = btf__snprintf(btf, _str, STRSIZE, _type_id, _ptr,\ + _hflags); \ + if (CHECK(_ret < 0, "btf snprintf", "failed: %d\n", \ + _ret)) \ + return; \ + _cmp = strncmp(_str, _expectedval, EXPECTED_STRSIZE); \ + if (CHECK(_cmp, "ensure expected/actual match", \ + "'%s' does not match expected '%s': %d\n", \ + _str, _expectedval, _cmp)) \ + return; \ + } while (0) + +/* Use where expected data string matches its stringified declaration */ +#define TEST_BTF_C(btf, _str, _type, _flags, ...) \ + TEST_BTF(btf, _str, _type, _flags, "(" #_type ")" #__VA_ARGS__, \ + __VA_ARGS__) + +/* Demonstrate that libbpf btf__snprintf succeeds and that various + * data types are formatted correctly. + */ +void test_snprintf_btf_user(void) +{ + struct btf *btf = libbpf_find_kernel_btf(); + int duration = 0; + char str[STRSIZE]; + + if (CHECK(!btf, "get kernel BTF", "no kernel BTF found")) + return; + + /* Verify type display for various types. */ + + /* simple int */ + TEST_BTF_C(btf, str, int, 0, 1234); + TEST_BTF(btf, str, int, BTF_F_NONAME, "1234", 1234); + + /* zero value should be printed at toplevel */ + TEST_BTF(btf, str, int, 0, "(int)0", 0); + TEST_BTF(btf, str, int, BTF_F_NONAME, "0", 0); + TEST_BTF(btf, str, int, BTF_F_ZERO, "(int)0", 0); + TEST_BTF(btf, str, int, BTF_F_NONAME | BTF_F_ZERO, "0", 0); + TEST_BTF_C(btf, str, int, 0, -4567); + TEST_BTF(btf, str, int, BTF_F_NONAME, "-4567", -4567); + + /* simple char */ + TEST_BTF_C(btf, str, char, 0, 100); + TEST_BTF(btf, str, char, BTF_F_NONAME, "100", 100); + /* zero value should be printed at toplevel */ + TEST_BTF(btf, str, char, 0, "(char)0", 0); + TEST_BTF(btf, str, char, BTF_F_NONAME, "0", 0); + TEST_BTF(btf, str, char, BTF_F_ZERO, "(char)0", 0); + TEST_BTF(btf, str, char, BTF_F_NONAME | BTF_F_ZERO, "0", 0); + + /* simple typedef */ + TEST_BTF_C(btf, str, uint64_t, 0, 100); + TEST_BTF(btf, str, u64, BTF_F_NONAME, "1", 1); + /* zero value should be printed at toplevel */ + TEST_BTF(btf, str, u64, 0, "(u64)0", 0); + TEST_BTF(btf, str, u64, BTF_F_NONAME, "0", 0); + TEST_BTF(btf, str, u64, BTF_F_ZERO, "(u64)0", 0); + TEST_BTF(btf, str, u64, BTF_F_NONAME|BTF_F_ZERO, "0", 0); + + /* typedef struct */ + TEST_BTF_C(btf, str, atomic_t, 0, {.counter = (int)1,}); + TEST_BTF(btf, str, atomic_t, BTF_F_NONAME, "{1,}", {.counter = 1,}); + /* typedef with 0 value should be printed at toplevel */ + TEST_BTF(btf, str, atomic_t, 0, "(atomic_t){}", {.counter = 0,}); + TEST_BTF(btf, str, atomic_t, BTF_F_NONAME, "{}", {.counter = 0,}); + TEST_BTF(btf,str, atomic_t, BTF_F_ZERO, "(atomic_t){.counter = (int)0,}", + {.counter = 0,}); + TEST_BTF(btf, str, atomic_t, BTF_F_NONAME|BTF_F_ZERO, + "{0,}", {.counter = 0,}); + + /* enum where enum value does (and does not) exist */ + TEST_BTF_C(btf, str, enum bpf_cmd, 0, BPF_MAP_CREATE); + TEST_BTF(btf, str, enum bpf_cmd, 0, "(enum bpf_cmd)BPF_MAP_CREATE", 0); + TEST_BTF(btf, str, enum bpf_cmd, BTF_F_NONAME, "BPF_MAP_CREATE", + BPF_MAP_CREATE); + TEST_BTF(btf, str, enum bpf_cmd, BTF_F_NONAME|BTF_F_ZERO, + "BPF_MAP_CREATE", 0); + + TEST_BTF(btf, str, enum bpf_cmd, BTF_F_ZERO, + "(enum bpf_cmd)BPF_MAP_CREATE", + BPF_MAP_CREATE); + TEST_BTF(btf, str, enum bpf_cmd, BTF_F_NONAME|BTF_F_ZERO, + "BPF_MAP_CREATE", BPF_MAP_CREATE); + TEST_BTF_C(btf, str, enum bpf_cmd, 0, 2000); + TEST_BTF(btf, str, enum bpf_cmd, BTF_F_NONAME, "2000", 2000); + + /* simple struct */ + TEST_BTF_C(btf, str, struct btf_enum, 0, + {.name_off = (__u32)3,.val = (__s32)-1,}); + TEST_BTF(btf, str, struct btf_enum, BTF_F_NONAME, "{3,-1,}", + { .name_off = 3, .val = -1,}); + TEST_BTF(btf, str, struct btf_enum, BTF_F_NONAME, "{-1,}", + { .name_off = 0, .val = -1,}); + TEST_BTF(btf, str, struct btf_enum, BTF_F_NONAME|BTF_F_ZERO, "{0,-1,}", + { .name_off = 0, .val = -1,}); + /* empty struct should be printed */ + TEST_BTF(btf, str, struct btf_enum, 0, "(struct btf_enum){}", + { .name_off = 0, .val = 0,}); + TEST_BTF(btf, str, struct btf_enum, BTF_F_NONAME, "{}", + { .name_off = 0, .val = 0,}); + TEST_BTF(btf, str, struct btf_enum, BTF_F_ZERO, + "(struct btf_enum){.name_off = (__u32)0,.val = (__s32)0,}", + { .name_off = 0, .val = 0,}); + + /* struct with pointers */ + TEST_BTF(btf, str, struct list_head, BTF_F_PTR_RAW, + "(struct list_head){.next = (struct list_head *)0x1,}", + { .next = (struct list_head *)1 }); + /* NULL pointer should not be displayed */ + TEST_BTF(btf, str, struct list_head, BTF_F_PTR_RAW, + "(struct list_head){}", + { .next = (struct list_head *)0 }); + + /* struct with char array */ + TEST_BTF(btf, str, struct bpf_prog_info, 0, + "(struct bpf_prog_info){.name = (char[])['f','o','o',],}", + { .name = "foo",}); + TEST_BTF(btf, str, struct bpf_prog_info, BTF_F_NONAME, + "{['f','o','o',],}", + {.name = "foo",}); + /* leading null char means do not display string */ + TEST_BTF(btf, str, struct bpf_prog_info, 0, + "(struct bpf_prog_info){}", + {.name = {'\0', 'f', 'o', 'o'}}); + /* handle non-printable characters */ + TEST_BTF(btf, str, struct bpf_prog_info, 0, + "(struct bpf_prog_info){.name = (char[])[1,2,3,],}", + { .name = {1, 2, 3, 0}}); + + /* struct with non-char array */ + TEST_BTF(btf, str, struct __sk_buff, 0, + "(struct __sk_buff){.cb = (__u32[])[1,2,3,4,5,],}", + { .cb = {1, 2, 3, 4, 5,},}); + TEST_BTF(btf, str, struct __sk_buff, BTF_F_NONAME, + "{[1,2,3,4,5,],}", + { .cb = { 1, 2, 3, 4, 5},}); + /* For non-char, arrays, show non-zero values only */ + TEST_BTF(btf, str, struct __sk_buff, 0, + "(struct __sk_buff){.cb = (__u32[])[1,],}", + { .cb = { 0, 0, 1, 0, 0},}); + + /* struct with bitfields */ + TEST_BTF_C(btf, str, struct bpf_insn, 0, + {.code = (__u8)1,.dst_reg = (__u8)0x2,.src_reg = (__u8)0x3,.off = (__s16)4,.imm = (__s32)5,}); + TEST_BTF(btf, str, struct bpf_insn, BTF_F_NONAME, "{1,0x2,0x3,4,5,}", + {.code = 1, .dst_reg = 0x2, .src_reg = 0x3, .off = 4, + .imm = 5,}); +}
On Mon, Jan 11, 2021 at 05:32:51PM +0000, Alan Maguire wrote:
The BPF Type Format (BTF) can be used in conjunction with the helper bpf_snprintf_btf() to display kernel data with type information.
This series generalizes that support and shares it with libbpf so that libbpf can display typed data. BTF display functionality is factored out of kernel/bpf/btf.c into kernel/bpf/btf_show_common.c, and that file is duplicated in tools/lib/bpf. Similarly, common definitions and inline functions needed for this support are extracted into include/linux/btf_common.h and this header is again duplicated in tools/lib/bpf.
I think duplication will inevitable cause code to diverge. Could you keep a single copy? Take a look at kernel/bpf/disasm.[ch] It compiled once for the kernel and once for bpftool. So should be possible to do something similar for this case as well?
linux-kselftest-mirror@lists.linaro.org